From 5097bb716c10df9233a84563a9656a098df6dd58 Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Sun, 22 Sep 2024 23:28:25 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Initializing=20HAVEN?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 46 +++++++++ .gitignore | 3 + README.md | 170 +++++++++++++++++++++++++++++++ blastr.go | 21 ++++ config.go | 120 ++++++++++++++++++++++ go.mod | 53 ++++++++++ go.sum | 276 +++++++++++++++++++++++++++++++++++++++++++++++++++ import.go | 137 +++++++++++++++++++++++++ init.go | 82 +++++++++++++++ main.go | 177 +++++++++++++++++++++++++++++++++ util.go | 13 +++ wot.go | 120 ++++++++++++++++++++++ 12 files changed, 1218 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 blastr.go create mode 100644 config.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 import.go create mode 100644 init.go create mode 100644 main.go create mode 100644 util.go create mode 100644 wot.go diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..95cf2eb --- /dev/null +++ b/.env.example @@ -0,0 +1,46 @@ +OWNER_NPUB="npub1utx00neqgqln72j22kej3ux7803c2k986henvvha4thuwfkper4s7r50e8" + +## Private Relay Settings +PRIVATE_RELAY_NAME="utxo's private relay" +PRIVATE_RELAY_NPUB="npub1utx00neqgqln72j22kej3ux7803c2k986henvvha4thuwfkper4s7r50e8" +PRIVATE_RELAY_DESCRIPTION="A safe place to store my drafts and ecash" +PRIVATE_RELAY_ICON="https://i.nostr.build/6G6wW.gif" + +## Chat Relay Settings +CHAT_RELAY_NAME="utxo's chat relay" +CHAT_RELAY_NPUB="npub1utx00neqgqln72j22kej3ux7803c2k986henvvha4thuwfkper4s7r50e8" +CHAT_RELAY_DESCRIPTION="a relay for private chats" +CHAT_RELAY_ICON="https://i.nostr.build/6G6wW.gif" +CHAT_RELAY_WOT_DEPTH=3 +CHAT_RELAY_WOT_REFRESH_INTERVAL_HOURS=24 +CHAT_RELAY_MINIMUM_FOLLOWERS=3 + +## Outbox Relay Settings +OUTBOX_RELAY_NAME="utxo's outbox relay" +OUTBOX_RELAY_NPUB="npub1utx00neqgqln72j22kej3ux7803c2k986henvvha4thuwfkper4s7r50e8" +OUTBOX_RELAY_DESCRIPTION="a relay for public messages" +OUTBOX_RELAY_ICON="https://i.nostr.build/6G6wW.gif" + +## Inbox Relay Settings +INBOX_RELAY_NAME="utxo's inbox relay" +INBOX_RELAY_NPUB="npub1utx00neqgqln72j22kej3ux7803c2k986henvvha4thuwfkper4s7r50e8" +INBOX_RELAY_DESCRIPTION="send your interactions with my notes here" +INBOX_RELAY_ICON="https://i.nostr.build/6G6wW.gif" +INBOX_PULL_INTERVAL_SECONDS=600 + +## Import Settings +IMPORT_START_DATE="2023-01-20" +IMPORT_QUERY_INTERVAL_SECONDS=600 +IMPORT_SEED_RELAYS="relay.damus.io,nos.lol,relay.nostr.band,relay.snort.social,nostr.land,nostr.mom,relay.nos.social,relay.primal.net,relay.nostr.bg,no.str.cr,nostr21.com,nostrue.com,relay.siamstr.com" + +## Backup Settings +BACKUP_PROVIDER="aws" + +## AWS Backup Settings - REQUIRED IF BACKUP_PROVIDER="aws" +AWS_ACCESS_KEY_ID="AKIA" +AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" +AWS_REGION="us-west-2" +AWS_BUCKET_NAME="utxo-relay-backups" + +## Blastr Settings +BLASTR_RELAYS="relay.damus.io,nos.lol,relay.nostr.band,relay.snort.social,nostr.land,nostr.mom,relay.nos.social,relay.primal.net,relay.nostr.bg,no.str.cr,nostr21.com,nostrue.com,relay.siamstr.com,wot.utxo.one,nostrelites.org,wot.nostr.party,wot.sovbit.host,wot.girino.org,relay.lnau.net,wot.siamstr.com,wot.sudocarlos.com,relay.otherstuff.fyi,relay.lexingtonbitcoin.org,wot.azzamo.net,wot.swarmstr.com,zap.watch,satsage.xyz,wons.calva.dev" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e168dc8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +db/ +haven \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1fdec06 --- /dev/null +++ b/README.md @@ -0,0 +1,170 @@ +# HAVEN + +HAVEN (High Availability Vault for Events on Nostr) is the most comprehensive personal relay for the Nostr network. It provides it's owner with a variety of different relays to connect to, and a web of trust to filter out bad actors. + +## Four Relays in One + +**Private Relay**: This relay is only accessible by the owner of the relay. It is used for drafts, ecash and other private notes that nobody can read or write to.haven + +**Chat Relay**: This relay is used to contact the owner by DM. Only people in the web of trust can send notes that are private chat kinds. + +**Inbox Relay**: This relay is used to send notes to the owner. Only people in the web of trust can send notes that are inbox kinds. Notes are pulled from other relays and stored in the inbox relay. + +**Outbox Relay**: This relay is used to send notes to other people. Anyone can read to this relay but only the owner can write to it. Notes sent to the outbox are also blasted to other relays. + +## Not So Dumb Relay Features + +**Web of Trust**: Protected from DM and Inbox spam by using a web of trust. + +**Inbox Relay**: Notes are pulled from other relays and stored in the inbox relay. + +**Cloud Backups**: Notes are backed up in the cloud and can be restored if the relay is lost. + +**Blastr**: Notes sent to the outbox are also blasted to other relays. + +**Import Old Notes**: Import your old notes and notes you're tagged in from other relays. + +## Prerequisites + +- **Go**: Ensure you have Go installed on your system. You can download it from [here](https://golang.org/dl/). +- **Build Essentials**: If you're using Linux, you may need to install build essentials. You can do this by running `sudo apt install build-essential`. + +## Setup Instructions + +Follow these steps to get the Haven Relay running on your local machine: + +### 1. Clone the repository + +```bash +git clone https://github.com/bitvora/haven.git +cd haven +``` + +### 2. Copy `.env.example` to `.env` + +You'll need to create an `.env` file based on the example provided in the repository. + +```bash +cp .env.example .env +``` + +### 3. Set your environment variables + +Open the `.env` file and set the necessary environment variables. + +### 4. Build the project + +Run the following command to build the relay: + +```bash +go build +``` + +### 5. Create a Systemd Service (optional) + +To have the relay run as a service, create a systemd unit file. Make sure to limit the memory usage to less than your system's total memory to prevent the relay from crashing the system. + +1. Create the file: + +```bash +sudo nano /etc/systemd/system/haven.service +``` + +2. Add the following contents: + +```ini +[Unit] +Description=Haven Relay +After=network.target + +[Service] +ExecStart=/home/ubuntu/haven/haven +WorkingDirectory=/home/ubuntu/haven +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +Replace `/path/to/` with the actual paths where you cloned the repository and stored the `.env` file. + +3. Reload systemd to recognize the new service: + +```bash +sudo systemctl daemon-reload +``` + +4. Start the service: + +```bash +sudo systemctl start haven +``` + +5. (Optional) Enable the service to start on boot: + +```bash +sudo systemctl enable haven +``` + +### 6. Serving over nginx (optional) + +You can serve the relay over nginx by adding the following configuration to your nginx configuration file: + +```nginx +server { + listen 80; + server_name yourdomain.com; + + location / { + proxy_pass http://localhost:3355; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} +``` + +Replace `yourdomain.com` with your actual domain name. + +After adding the configuration, restart nginx: + +```bash +sudo systemctl restart nginx +``` + +### 7. Install Certbot (optional) + +If you want to serve the relay over HTTPS, you can use Certbot to generate an SSL certificate. + +```bash +sudo apt-get update +sudo apt-get install certbot python3-certbot-nginx +``` + +After installing Certbot, run the following command to generate an SSL certificate: + +```bash +sudo certbot --nginx +``` + +Follow the instructions to generate the certificate. + +### 8. Run The Import (optional) + +If you want to import your old notes and notes you're tagged in from other relays, run the following command: + +```bash +./haven --import +``` + +### 9. Access the relay + +Once everything is set up, the relay will be running on `localhost:3355` or your domain name if you set up nginx. + +## License + +This project is licensed under the MIT License. diff --git a/blastr.go b/blastr.go new file mode 100644 index 0000000..cf36c25 --- /dev/null +++ b/blastr.go @@ -0,0 +1,21 @@ +package main + +import ( + "context" + "log" + + "github.com/nbd-wtf/go-nostr" +) + +func blast(ev *nostr.Event) { + ctx := context.Background() + for _, relay := range config.BlastrRelays { + log.Println("🔫 blasting to", relay) + connect, err := nostr.RelayConnect(ctx, relay) + if err != nil { + log.Println("error connecting to relay", relay, err) + continue + } + connect.Publish(ctx, *ev) + } +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..14374a5 --- /dev/null +++ b/config.go @@ -0,0 +1,120 @@ +package main + +import ( + "log" + "os" + "strconv" + "strings" + + "github.com/joho/godotenv" +) + +type Config struct { + OwnerNpub string `json:"owner_npub"` + RelaySoftware string `json:"relay_software"` + RelayVersion string `json:"relay_version"` + PrivateRelayName string `json:"private_relay_name"` + PrivateRelayNpub string `json:"private_relay_npub"` + PrivateRelayDescription string `json:"private_relay_description"` + PrivateRelayIcon string `json:"private_relay_icon"` + ChatRelayName string `json:"chat_relay_name"` + ChatRelayNpub string `json:"chat_relay_npub"` + ChatRelayDescription string `json:"chat_relay_description"` + ChatRelayIcon string `json:"chat_relay_icon"` + ChatRelayWotDepth int `json:"chat_relay_wot_depth"` + ChatRelayWotRefreshIntervalHours int `json:"chat_relay_wot_refresh_interval_hours"` + ChatRelayMinimumFollowers int `json:"chat_relay_minimum_followers"` + OutboxRelayName string `json:"outbox_relay_name"` + OutboxRelayNpub string `json:"outbox_relay_npub"` + OutboxRelayDescription string `json:"outbox_relay_description"` + OutboxRelayIcon string `json:"outbox_relay_icon"` + InboxRelayName string `json:"inbox_relay_name"` + InboxRelayNpub string `json:"inbox_relay_npub"` + InboxRelayDescription string `json:"inbox_relay_description"` + InboxRelayIcon string `json:"inbox_relay_icon"` + InboxPullIntervalSeconds int `json:"inbox_pull_interval_seconds"` + ImportStartDate string `json:"import_start_date"` + ImportQueryIntervalSeconds int `json:"import_query_interval_seconds"` + ImportSeedRelays []string `json:"import_seed_relays"` + BackupProvider string `json:"backup_provider"` + BlastrRelays []string `json:"blastr_relays"` +} + +type AwsConfig struct { + AccessKeyID string `json:"access"` + SecretAccessKey string `json:"secret"` + Region string `json:"region"` + Bucket string `json:"bucket"` +} + +func loadConfig() Config { + godotenv.Load(".env") + + return Config{ + OwnerNpub: getEnv("OWNER_NPUB"), + RelaySoftware: "https://github.com/bitvora/haven", + RelayVersion: "v0.1.0", + PrivateRelayName: getEnv("PRIVATE_RELAY_NAME"), + PrivateRelayNpub: getEnv("PRIVATE_RELAY_NPUB"), + PrivateRelayDescription: getEnv("PRIVATE_RELAY_DESCRIPTION"), + PrivateRelayIcon: getEnv("PRIVATE_RELAY_ICON"), + ChatRelayName: getEnv("CHAT_RELAY_NAME"), + ChatRelayNpub: getEnv("CHAT_RELAY_NPUB"), + ChatRelayDescription: getEnv("CHAT_RELAY_DESCRIPTION"), + ChatRelayIcon: getEnv("CHAT_RELAY_ICON"), + ChatRelayWotDepth: getEnvInt("CHAT_RELAY_WOT_DEPTH", 0), + ChatRelayWotRefreshIntervalHours: getEnvInt("CHAT_RELAY_WOT_REFRESH_INTERVAL_HOURS", 0), + ChatRelayMinimumFollowers: getEnvInt("CHAT_RELAY_MINIMUM_FOLLOWERS", 0), + OutboxRelayName: getEnv("OUTBOX_RELAY_NAME"), + OutboxRelayNpub: getEnv("OUTBOX_RELAY_NPUB"), + OutboxRelayDescription: getEnv("OUTBOX_RELAY_DESCRIPTION"), + OutboxRelayIcon: getEnv("OUTBOX_RELAY_ICON"), + InboxRelayName: getEnv("INBOX_RELAY_NAME"), + InboxRelayNpub: getEnv("INBOX_RELAY_NPUB"), + InboxRelayDescription: getEnv("INBOX_RELAY_DESCRIPTION"), + InboxRelayIcon: getEnv("INBOX_RELAY_ICON"), + InboxPullIntervalSeconds: getEnvInt("INBOX_PULL_INTERVAL_SECONDS", 3600), + ImportStartDate: getEnv("IMPORT_START_DATE"), + ImportQueryIntervalSeconds: getEnvInt("IMPORT_QUERY_INTERVAL_SECONDS", 360000), + ImportSeedRelays: getRelayList(getEnv("IMPORT_SEED_RELAYS")), + BackupProvider: getEnv("BACKUP_PROVIDER"), + BlastrRelays: getRelayList(getEnv("BLASTR_RELAYS")), + } +} + +func getRelayList(commaList string) []string { + relayList := strings.Split(commaList, ",") + for i, relay := range relayList { + relayList[i] = "wss://" + strings.TrimSpace(relay) + } + return relayList +} + +func getEnv(key string) string { + value, exists := os.LookupEnv(key) + if !exists { + log.Fatalf("Environment variable %s not set", key) + } + return value +} + +func getEnvInt(key string, defaultValue int) int { + if value, ok := os.LookupEnv(key); ok { + intValue, err := strconv.Atoi(value) + if err != nil { + panic(err) + } + return intValue + } + return defaultValue +} + +var art = ` +██╗ ██╗ █████╗ ██╗ ██╗███████╗███╗ ██╗ +██║ ██║██╔══██╗██║ ██║██╔════╝████╗ ██║ +███████║███████║██║ ██║█████╗ ██╔██╗ ██║ +██╔══██║██╔══██║╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ +██║ ██║██║ ██║ ╚████╔╝ ███████╗██║ ╚████║ +╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ +HIGH AVAILABILITY VAULT FOR EVENTS ON NOSTR + ` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d82a201 --- /dev/null +++ b/go.mod @@ -0,0 +1,53 @@ +module github.com/bitvora/haven + +go 1.23.0 + +toolchain go1.23.1 + +require ( + github.com/fiatjaf/eventstore v0.9.0 + github.com/fiatjaf/khatru v0.8.1 + github.com/joho/godotenv v1.5.1 + github.com/nbd-wtf/go-nostr v0.37.2 +) + +require ( + github.com/PowerDNS/lmdb-go v1.9.2 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/btcsuite/btcd/btcutil v1.1.3 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect + github.com/dgraph-io/badger/v4 v4.2.0 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fasthttp/websocket v1.5.7 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect + github.com/gobwas/ws v1.4.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.1.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/flatbuffers v23.5.26+incompatible // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.17.8 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/rs/cors v1.7.0 // indirect + github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect + github.com/tidwall/gjson v1.17.3 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + go.opencensus.io v0.24.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.25.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8a08d60 --- /dev/null +++ b/go.sum @@ -0,0 +1,276 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/PowerDNS/lmdb-go v1.9.2 h1:Cmgerh9y3ZKBZGz1irxSShhfmFyRUh+Zdk4cZk7ZJvU= +github.com/PowerDNS/lmdb-go v1.9.2/go.mod h1:TE0l+EZK8Z1B4dx070ZxkWTlp8RG1mjN0/+FkFRQMtU= +github.com/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.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +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.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +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.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +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.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.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +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.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +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/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/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/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/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= +github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fasthttp/websocket v1.5.7 h1:0a6o2OfeATvtGgoMKleURhLT6JqWPg7fYfWnH4KHau4= +github.com/fasthttp/websocket v1.5.7/go.mod h1:bC4fxSono9czeXHQUVKxsC0sNjbm7lPJR04GDFqClfU= +github.com/fiatjaf/eventstore v0.8.2 h1:nCa3UuJNV5Y5t+SDoPQe7PBmKJ6dhm9TQ/WyR4SCbIM= +github.com/fiatjaf/eventstore v0.8.2/go.mod h1:ck3RxufitHUBjID1RLcRxfX+NMywQzMsdfNpSt6m+9U= +github.com/fiatjaf/eventstore v0.9.0 h1:WsGDVAaRaVaV/J8PdrQDGfzChrL13q+lTO4C44rhu3E= +github.com/fiatjaf/eventstore v0.9.0/go.mod h1:JrAce5h0wi79+Sw4gsEq5kz0NtUxbVkOZ7lAo7ay6R8= +github.com/fiatjaf/khatru v0.8.1 h1:BWAZqwuT0272ZlyzPkuqAA0eGBOs5G3u0Dn1tlWrm6Q= +github.com/fiatjaf/khatru v0.8.1/go.mod h1:jRmqbbIbEH+y0unt3wMUBwqY/btVussqx5SmBoGhXtg= +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/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.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= +github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +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/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/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.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8ioaQmyPLg1b8VwK5WJg= +github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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 v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +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/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/nbd-wtf/go-nostr v0.35.0 h1:oINIBr5XE1kowkaz7NXC5vLvj2jUWH6xlzJjChpgV6Q= +github.com/nbd-wtf/go-nostr v0.35.0/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs= +github.com/nbd-wtf/go-nostr v0.36.3 h1:50fNFO8vQNMEIZ+6qUq0M5hlqEtA13WrtrKcz10eg9k= +github.com/nbd-wtf/go-nostr v0.36.3/go.mod h1:TGKGj00BmJRXvRe0LlpDN3KKbELhhPXgBwUEhzu3Oq0= +github.com/nbd-wtf/go-nostr v0.37.2 h1:42rriFqqz07EdydERwYeQnewl+Rah1Gq46I+Wh0KYYg= +github.com/nbd-wtf/go-nostr v0.37.2/go.mod h1:TGKGj00BmJRXvRe0LlpDN3KKbELhhPXgBwUEhzu3Oq0= +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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= +github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= +github.com/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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.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/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= +github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +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.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/import.go b/import.go new file mode 100644 index 0000000..4a4238e --- /dev/null +++ b/import.go @@ -0,0 +1,137 @@ +package main + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/fiatjaf/eventstore" + "github.com/nbd-wtf/go-nostr" +) + +const layout = "2006-01-02" + +var ownerImportedNotes = 0 +var taggedImportedNotes = 0 + +func importOwnerNotes() { + ctx := context.Background() + pool = nostr.NewSimplePool(ctx) + wdb := eventstore.RelayWrapper{Store: &outboxDB} + + startTime, err := time.Parse(layout, config.ImportStartDate) + if err != nil { + fmt.Println("Error parsing start date:", err) + return + } + endTime := startTime.Add(240 * time.Hour) + + for { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + startTimestamp := nostr.Timestamp(startTime.Unix()) + endTimestamp := nostr.Timestamp(endTime.Unix()) + + filters := []nostr.Filter{{ + Authors: []string{nPubToPubkey(config.OwnerNpub)}, + Since: &startTimestamp, + Until: &endTimestamp, + }} + + for ev := range pool.SubManyEose(ctx, config.ImportSeedRelays, filters) { + wdb.Publish(ctx, *ev.Event) + ownerImportedNotes++ + } + log.Println("📦 imported", ownerImportedNotes, "owner notes") + time.Sleep(5 * time.Second) + + startTime = startTime.Add(240 * time.Hour) + endTime = endTime.Add(240 * time.Hour) + + if startTime.After(time.Now()) { + log.Println("✅ owner note import complete! ") + break + } + } +} + +func importTaggedNotes() { + ctx := context.Background() + pool = nostr.NewSimplePool(ctx) + wdb := eventstore.RelayWrapper{Store: &inboxDB} + + startTime, err := time.Parse(layout, config.ImportStartDate) + if err != nil { + fmt.Println("Error parsing start date:", err) + return + } + endTime := startTime.Add(240 * time.Hour) + + for { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + startTimestamp := nostr.Timestamp(startTime.Unix()) + endTimestamp := nostr.Timestamp(endTime.Unix()) + + filters := []nostr.Filter{{ + Tags: nostr.TagMap{ + "p": {nPubToPubkey(config.OwnerNpub)}, + }, + Since: &startTimestamp, + Until: &endTimestamp, + }} + + for ev := range pool.SubManyEose(ctx, config.ImportSeedRelays, filters) { + wdb.Publish(ctx, *ev.Event) + taggedImportedNotes++ + } + log.Println("📦 imported", taggedImportedNotes, "tagged notes") + time.Sleep(5 * time.Second) + + startTime = startTime.Add(240 * time.Hour) + endTime = endTime.Add(240 * time.Hour) + + if startTime.After(time.Now()) { + log.Println("✅ tagged import complete. please restart the relay") + break + } + } +} + +func subscribeInbox() { + ctx := context.Background() + wdb := eventstore.RelayWrapper{Store: &inboxDB} + filters := []nostr.Filter{{ + Tags: nostr.TagMap{ + "p": {nPubToPubkey(config.OwnerNpub)}, + }, + }} + + log.Println("📢 subscribing to inbox") + for ev := range pool.SubMany(ctx, config.ImportSeedRelays, filters) { + if !wotMap[ev.Event.PubKey] { + continue + } + + wdb.Publish(ctx, *ev.Event) + switch ev.Event.Kind { + case nostr.KindTextNote: + log.Println("📰 new note in your inbox") + case nostr.KindReaction: + log.Println(ev.Event.Content, "new reaction in your inbox") + case nostr.KindZap: + log.Println("⚡️ new zap in your inbox") + case nostr.KindEncryptedDirectMessage: + log.Println("🔒 new encrypted message in your inbox") + case nostr.KindRepost: + log.Println("🔁 new repost in your inbox") + case nostr.KindFollowList: + // do nothing + default: + log.Println("📦 new event in your inbox") + } + } +} diff --git a/init.go b/init.go new file mode 100644 index 0000000..caf972a --- /dev/null +++ b/init.go @@ -0,0 +1,82 @@ +package main + +import ( + "github.com/fiatjaf/eventstore/badger" + "github.com/fiatjaf/khatru" +) + +var privateRelay = khatru.NewRelay() +var privateDB = getPrivateDB() + +var chatRelay = khatru.NewRelay() +var chatDB = getChatDB() + +var outboxRelay = khatru.NewRelay() +var outboxDB = getOutboxDB() + +var inboxRelay = khatru.NewRelay() +var inboxDB = getInboxDB() + +func getPrivateDB() badger.BadgerBackend { + return badger.BadgerBackend{ + Path: "db/private", + } +} + +func getChatDB() badger.BadgerBackend { + return badger.BadgerBackend{ + Path: "db/chat", + } +} + +func getOutboxDB() badger.BadgerBackend { + return badger.BadgerBackend{ + Path: "db/outbox", + } +} + +func getInboxDB() badger.BadgerBackend { + return badger.BadgerBackend{ + Path: "db/inbox", + } +} + +func initRelays() { + + if err := privateDB.Init(); err != nil { + panic(err) + } + + if err := chatDB.Init(); err != nil { + panic(err) + } + + if err := outboxDB.Init(); err != nil { + panic(err) + } + + if err := inboxDB.Init(); err != nil { + panic(err) + } + + privateRelay.Info.Name = config.PrivateRelayName + privateRelay.Info.PubKey = nPubToPubkey(config.PrivateRelayNpub) + privateRelay.Info.Description = config.PrivateRelayDescription + privateRelay.Info.Icon = config.PrivateRelayIcon + privateRelay.Info.Version = config.RelayVersion + privateRelay.Info.Software = config.RelaySoftware + + chatRelay.Info.Name = config.ChatRelayName + chatRelay.Info.PubKey = nPubToPubkey(config.ChatRelayNpub) + chatRelay.Info.Description = config.ChatRelayDescription + chatRelay.Info.Icon = config.ChatRelayIcon + chatRelay.Info.Version = config.RelayVersion + chatRelay.Info.Software = config.RelaySoftware + + outboxRelay.Info.Name = config.OutboxRelayName + outboxRelay.Info.PubKey = nPubToPubkey(config.OutboxRelayNpub) + outboxRelay.Info.Description = config.OutboxRelayDescription + outboxRelay.Info.Icon = config.OutboxRelayIcon + outboxRelay.Info.Version = config.RelayVersion + outboxRelay.Info.Software = config.RelaySoftware +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..2dbba72 --- /dev/null +++ b/main.go @@ -0,0 +1,177 @@ +package main + +import ( + "context" + "flag" + "fmt" + "io" + "log" + "net/http" + + "github.com/fiatjaf/khatru" + "github.com/nbd-wtf/go-nostr" + "github.com/puzpuzpuz/xsync/v3" +) + +var mainRelay = khatru.NewRelay() +var subRelays = xsync.NewMapOf[string, *khatru.Relay]() +var pool *nostr.SimplePool +var config = loadConfig() + +func main() { + importFlag := flag.Bool("import", false, "Run the importNotes function after initializing relays") + flag.Parse() + + nostr.InfoLogger = log.New(io.Discard, "", 0) + green := "\033[32m" + reset := "\033[0m" + fmt.Println(green + art + reset) + log.Println("🚀 haven is booting up") + initRelays() + + if *importFlag { + log.Println("📦 importing notes") + importOwnerNotes() + importTaggedNotes() + return + } + + go refreshTrustNetwork() + go subscribeInbox() + + handler := http.HandlerFunc(dynamicRelayHandler) + + log.Printf("🔗 listening at http://localhost:3355") + http.ListenAndServe("0.0.0.0:3355", handler) +} + +func dynamicRelayHandler(w http.ResponseWriter, r *http.Request) { + var relay *khatru.Relay + relayType := r.URL.Path + + if relayType == "" { + relay = mainRelay + } else { + relay, _ = subRelays.LoadOrCompute(relayType, func() *khatru.Relay { + return makeNewRelay(relayType) + }) + } + + relay.ServeHTTP(w, r) +} + +func makeNewRelay(relayType string) *khatru.Relay { + switch relayType { + case "/private": + privateRelay.OnConnect = append(privateRelay.OnConnect, func(ctx context.Context) { + khatru.RequestAuth(ctx) + }) + + privateRelay.StoreEvent = append(privateRelay.StoreEvent, privateDB.SaveEvent) + privateRelay.QueryEvents = append(privateRelay.QueryEvents, privateDB.QueryEvents) + privateRelay.DeleteEvent = append(privateRelay.DeleteEvent, privateDB.DeleteEvent) + + privateRelay.RejectFilter = append(privateRelay.RejectFilter, func(ctx context.Context, filter nostr.Filter) (bool, string) { + authenticatedUser := khatru.GetAuthed(ctx) + + if authenticatedUser == privateRelay.Info.PubKey { + return false, "" + } + + return true, "only the owner can access this relay" + }) + + return privateRelay + + case "/chat": + chatRelay.OnConnect = append(chatRelay.OnConnect, func(ctx context.Context) { + khatru.RequestAuth(ctx) + }) + + chatRelay.StoreEvent = append(chatRelay.StoreEvent, chatDB.SaveEvent) + chatRelay.QueryEvents = append(chatRelay.QueryEvents, chatDB.QueryEvents) + chatRelay.DeleteEvent = append(chatRelay.DeleteEvent, chatDB.DeleteEvent) + + chatRelay.RejectFilter = append(chatRelay.RejectFilter, func(ctx context.Context, filter nostr.Filter) (bool, string) { + authenticatedUser := khatru.GetAuthed(ctx) + + if !wotMap[authenticatedUser] { + return true, "you must be in the web of trust to chat with the relay owner" + } + + return false, "" + }) + + allowedKinds := []int{ + nostr.KindEncryptedDirectMessage, + nostr.KindSimpleGroupAddPermission, + nostr.KindSimpleGroupAddUser, + nostr.KindSimpleGroupAdmins, + nostr.KindSimpleGroupChatMessage, + nostr.KindSimpleGroupCreateGroup, + nostr.KindSimpleGroupDeleteEvent, + nostr.KindSimpleGroupDeleteGroup, + nostr.KindSimpleGroupEditGroupStatus, + nostr.KindSimpleGroupEditMetadata, + nostr.KindSimpleGroupJoinRequest, + nostr.KindSimpleGroupLeaveRequest, + nostr.KindSimpleGroupMembers, + nostr.KindSimpleGroupMetadata, + nostr.KindSimpleGroupRemovePermission, + nostr.KindSimpleGroupRemoveUser, + nostr.KindSimpleGroupReply, + nostr.KindSimpleGroupThread, + nostr.KindChannelHideMessage, + nostr.KindChannelMessage, + } + + chatRelay.RejectEvent = append(chatRelay.RejectEvent, func(ctx context.Context, event *nostr.Event) (bool, string) { + for _, kind := range allowedKinds { + if event.Kind == kind { + return false, "" + } + } + + return true, "only direct messages are allowed in this relay" + }) + + return chatRelay + + case "/inbox": + inboxRelay.StoreEvent = append(inboxRelay.StoreEvent, inboxDB.SaveEvent) + inboxRelay.QueryEvents = append(inboxRelay.QueryEvents, inboxDB.QueryEvents) + + inboxRelay.RejectEvent = append(inboxRelay.RejectEvent, func(ctx context.Context, event *nostr.Event) (bool, string) { + if !wotMap[event.PubKey] { + return true, "you must be in the web of trust to post to this relay" + } + + for _, tag := range event.Tags.GetAll([]string{"p"}) { + if tag[1] == inboxRelay.Info.PubKey { + return false, "" + } + } + + return true, "you can only post notes if you've tagged the owner of this relay" + }) + + return inboxRelay + + default: // default to outbox + outboxRelay.StoreEvent = append(outboxRelay.StoreEvent, outboxDB.SaveEvent, func(ctx context.Context, event *nostr.Event) error { + go blast(event) + return nil + }) + outboxRelay.QueryEvents = append(outboxRelay.QueryEvents, outboxDB.QueryEvents) + outboxRelay.DeleteEvent = append(outboxRelay.DeleteEvent, outboxDB.DeleteEvent) + + outboxRelay.RejectEvent = append(outboxRelay.RejectEvent, func(ctx context.Context, event *nostr.Event) (bool, string) { + if event.PubKey == outboxRelay.Info.PubKey { + return false, "" + } + return true, "you are not allowed to post to this relay" + }) + + return outboxRelay + } +} diff --git a/util.go b/util.go new file mode 100644 index 0000000..1dbebde --- /dev/null +++ b/util.go @@ -0,0 +1,13 @@ +package main + +import ( + "github.com/nbd-wtf/go-nostr/nip19" +) + +func nPubToPubkey(nPub string) string { + _, v, err := nip19.Decode(nPub) + if err != nil { + panic(err) + } + return v.(string) +} diff --git a/wot.go b/wot.go new file mode 100644 index 0000000..c4aca36 --- /dev/null +++ b/wot.go @@ -0,0 +1,120 @@ +package main + +import ( + "context" + "log" + "time" + + "github.com/nbd-wtf/go-nostr" +) + +var pubkeyFollowerCount = make(map[string]int) +var oneHopNetwork []string +var wot []string +var wotRelays []string +var wotMap map[string]bool + +func refreshTrustNetwork() { + + ctx := context.Background() + timeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second) + pool = nostr.NewSimplePool(ctx) + + defer cancel() + ownerPubkey := nPubToPubkey(config.OwnerNpub) + + filters := []nostr.Filter{{ + Authors: []string{ownerPubkey}, + Kinds: []int{nostr.KindFollowList}, + }} + + for ev := range pool.SubManyEose(timeoutCtx, config.ImportSeedRelays, filters) { + for _, contact := range ev.Event.Tags.GetAll([]string{"p"}) { + pubkeyFollowerCount[contact[1]]++ + appendOneHopNetwork(contact[1]) + } + } + + log.Println("🌐 building web of trust graph") + for i := 0; i < len(oneHopNetwork); i += 100 { + timeout, cancel := context.WithTimeout(ctx, 4*time.Second) + defer cancel() + + end := i + 100 + if end > len(oneHopNetwork) { + end = len(oneHopNetwork) + } + + filters = []nostr.Filter{{ + Authors: oneHopNetwork[i:end], + Kinds: []int{nostr.KindFollowList, nostr.KindRelayListMetadata}, + }} + + for ev := range pool.SubManyEose(timeout, config.ImportSeedRelays, filters) { + for _, contact := range ev.Event.Tags.GetAll([]string{"p"}) { + if len(contact) > 1 { + pubkeyFollowerCount[contact[1]]++ + } + } + + for _, relay := range ev.Event.Tags.GetAll([]string{"r"}) { + appendRelay(relay[1]) + } + + } + } + log.Println("🫂 total network size:", len(pubkeyFollowerCount)) + log.Println("🔗 relays discovered:", len(wotRelays)) + updateWoTMap() +} + +func appendRelay(relay string) { + + for _, r := range wotRelays { + if r == relay { + return + } + } + wotRelays = append(wotRelays, relay) +} + +func appendPubkeyToWoT(pubkey string) { + for _, pk := range wot { + if pk == pubkey { + return + } + } + + if len(pubkey) != 64 { + return + } + + wot = append(wot, pubkey) +} + +func appendOneHopNetwork(pubkey string) { + for _, pk := range oneHopNetwork { + if pk == pubkey { + return + } + } + + if len(pubkey) != 64 { + return + } + + oneHopNetwork = append(oneHopNetwork, pubkey) +} + +func updateWoTMap() { + wotMap = make(map[string]bool) + + for pubkey, count := range pubkeyFollowerCount { + if count >= config.ChatRelayMinimumFollowers { + wotMap[pubkey] = true + appendPubkeyToWoT(pubkey) + } + } + + log.Println("🌐 pubkeys with minimum followers: ", len(wotMap), "keys") +}