Merge pull request #8 from coracle-social/supported-nips
Add push support
This commit is contained in:
@@ -59,6 +59,12 @@ Configures blossom support.
|
|||||||
|
|
||||||
- `enabled` - whether blossom is enabled.
|
- `enabled` - whether blossom is enabled.
|
||||||
|
|
||||||
|
### `[push]`
|
||||||
|
|
||||||
|
Configures NIP 9a push support.
|
||||||
|
|
||||||
|
- `enabled` - whether push is enabled.
|
||||||
|
|
||||||
### `[roles]`
|
### `[roles]`
|
||||||
|
|
||||||
Defines roles that can be assigned to different users and attendant privileges. Each role is defined by a `[roles.{role_name}]` header and has the following options:
|
Defines roles that can be assigned to different users and attendant privileges. Each role is defined by a `[roles.{role_name}]` header and has the following options:
|
||||||
@@ -99,6 +105,9 @@ methods = ["supportedmethods", "banpubkey", "allowpubkey"]
|
|||||||
[blossom]
|
[blossom]
|
||||||
enabled = false
|
enabled = false
|
||||||
|
|
||||||
|
[push]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
[roles.member]
|
[roles.member]
|
||||||
can_invite = true
|
can_invite = true
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -28,8 +28,8 @@ func main() {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
relay = flag.String("relay", "", "Relay name (required)")
|
relay = flag.String("relay", "", "Relay name (required)")
|
||||||
reset = flag.Bool("reset", false, "Delete all events from the store before importing")
|
reset = flag.Bool("reset", false, "Delete all events from the store before importing")
|
||||||
force = flag.Bool("force", false, "Skip validation prompts and import valid events only")
|
force = flag.Bool("force", false, "Skip validation prompts and import valid events only")
|
||||||
)
|
)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module zooid
|
module zooid
|
||||||
|
|
||||||
go 1.24.1
|
go 1.25
|
||||||
|
|
||||||
require (
|
require (
|
||||||
fiatjaf.com/nostr v0.0.0-20251104112613-38a6ca92b954
|
fiatjaf.com/nostr v0.0.0-20251104112613-38a6ca92b954
|
||||||
@@ -35,6 +35,8 @@ require (
|
|||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||||
github.com/rs/cors v1.11.1 // indirect
|
github.com/rs/cors v1.11.1 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||||
|
github.com/templexxx/cpu v0.0.1 // indirect
|
||||||
|
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b // indirect
|
||||||
github.com/tidwall/gjson v1.18.0 // indirect
|
github.com/tidwall/gjson v1.18.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
|
||||||
@@ -42,6 +44,9 @@ require (
|
|||||||
github.com/valyala/fasthttp v1.59.0 // indirect
|
github.com/valyala/fasthttp v1.59.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||||
golang.org/x/net v0.41.0 // indirect
|
golang.org/x/net v0.41.0 // indirect
|
||||||
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
golang.org/x/sys v0.35.0 // indirect
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
golang.org/x/text v0.28.0 // indirect
|
golang.org/x/text v0.28.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace fiatjaf.com/nostr => git.coracle.social/Coracle/nostrlib v0.0.0-20260209224037-43de47addbce
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
fiatjaf.com/nostr v0.0.0-20250924142401-59bd3c29fffd h1:LnbRz+TxZAROXglKFT+Lqsdqe5Pu8PG0rSpmXGnES90=
|
git.coracle.social/Coracle/nostrlib v0.0.0-20260209224037-43de47addbce h1:FG5FSVNoA37kcojItd0dKfK/o97BitPPFA5+ZUVcQT8=
|
||||||
fiatjaf.com/nostr v0.0.0-20250924142401-59bd3c29fffd/go.mod h1:Nq86Jjsd0OmsOEImUg0iCcLuqM5B67Nj2eu/2dP74Ss=
|
git.coracle.social/Coracle/nostrlib v0.0.0-20260209224037-43de47addbce/go.mod h1:ue7yw0zHfZj23Ml2kVSdBx0ENEaZiuvGxs/8VEN93FU=
|
||||||
fiatjaf.com/nostr v0.0.0-20251104112613-38a6ca92b954 h1:CMD8D3TgEjGhuIBNMnvZ0EXOW0JR9O3w8AI6Yuzt8Ec=
|
|
||||||
fiatjaf.com/nostr v0.0.0-20251104112613-38a6ca92b954/go.mod h1:Nq86Jjsd0OmsOEImUg0iCcLuqM5B67Nj2eu/2dP74Ss=
|
|
||||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 h1:ClzzXMDDuUbWfNNZqGeYq4PnYOlwlOVIvSyNaIy0ykg=
|
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 h1:ClzzXMDDuUbWfNNZqGeYq4PnYOlwlOVIvSyNaIy0ykg=
|
||||||
@@ -14,8 +12,11 @@ github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7X
|
|||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
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 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||||
|
github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY=
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
|
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/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||||
|
github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8=
|
||||||
|
github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00=
|
||||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ=
|
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/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||||
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
||||||
@@ -32,8 +33,8 @@ github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOU
|
|||||||
github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg=
|
github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
|
github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
|
||||||
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
|
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
|
||||||
@@ -78,6 +79,10 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
|||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
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/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/templexxx/cpu v0.0.1 h1:hY4WdLOgKdc8y13EYklu9OUTXik80BkxHoWvTO6MQQY=
|
||||||
|
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||||
|
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b h1:XeDLE6c9mzHpdv3Wb1+pWBaWv/BlHK0ZYIu/KaL6eHg=
|
||||||
|
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b/go.mod h1:7rwmCH0wC2fQvNEvPZ3sKXukhyCTyiaZ5VTZMQYpZKQ=
|
||||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.18.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=
|
||||||
@@ -91,10 +96,14 @@ github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDp
|
|||||||
github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU=
|
github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||||
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ type Config struct {
|
|||||||
AutoJoin bool `toml:"auto_join"`
|
AutoJoin bool `toml:"auto_join"`
|
||||||
} `toml:"groups"`
|
} `toml:"groups"`
|
||||||
|
|
||||||
|
Push struct {
|
||||||
|
Enabled bool `toml:"enabled"`
|
||||||
|
} `toml:"push"`
|
||||||
|
|
||||||
Management struct {
|
Management struct {
|
||||||
Enabled bool `toml:"enabled"`
|
Enabled bool `toml:"enabled"`
|
||||||
Methods []string `toml:"methods"`
|
Methods []string `toml:"methods"`
|
||||||
|
|||||||
+1
-1
@@ -332,5 +332,5 @@ func (g *GroupStore) CheckWrite(event nostr.Event) string {
|
|||||||
// Middleware
|
// Middleware
|
||||||
|
|
||||||
func (g *GroupStore) Enable(instance *Instance) {
|
func (g *GroupStore) Enable(instance *Instance) {
|
||||||
instance.Relay.Info.SupportedNIPs = append(instance.Relay.Info.SupportedNIPs, 29)
|
instance.Relay.Info.SupportedNIPs = append(instance.Relay.Info.SupportedNIPs, "29")
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-51
@@ -6,7 +6,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/khatru"
|
"fiatjaf.com/nostr/khatru"
|
||||||
@@ -20,6 +19,7 @@ type Instance struct {
|
|||||||
Blossom *BlossomStore
|
Blossom *BlossomStore
|
||||||
Management *ManagementStore
|
Management *ManagementStore
|
||||||
Groups *GroupStore
|
Groups *GroupStore
|
||||||
|
Push *PushManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeInstance(filename string) (*Instance, error) {
|
func MakeInstance(filename string) (*Instance, error) {
|
||||||
@@ -54,6 +54,13 @@ func MakeInstance(filename string) (*Instance, error) {
|
|||||||
Management: management,
|
Management: management,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
push := &PushManager{
|
||||||
|
Config: config,
|
||||||
|
Events: events,
|
||||||
|
Management: management,
|
||||||
|
Groups: groups,
|
||||||
|
}
|
||||||
|
|
||||||
instance := &Instance{
|
instance := &Instance{
|
||||||
Relay: relay,
|
Relay: relay,
|
||||||
Config: config,
|
Config: config,
|
||||||
@@ -61,22 +68,23 @@ func MakeInstance(filename string) (*Instance, error) {
|
|||||||
Blossom: blossom,
|
Blossom: blossom,
|
||||||
Management: management,
|
Management: management,
|
||||||
Groups: groups,
|
Groups: groups,
|
||||||
|
Push: push,
|
||||||
}
|
}
|
||||||
|
|
||||||
// NIP 11 info
|
// NIP 11 info
|
||||||
|
|
||||||
// self := config.GetSelf()
|
self := config.GetSelf()
|
||||||
owner := config.GetOwner()
|
owner := config.GetOwner()
|
||||||
|
|
||||||
instance.Relay.Negentropy = true
|
instance.Relay.Negentropy = true
|
||||||
instance.Relay.Info.Name = config.Info.Name
|
instance.Relay.Info.Name = config.Info.Name
|
||||||
instance.Relay.Info.Icon = config.Info.Icon
|
instance.Relay.Info.Icon = config.Info.Icon
|
||||||
// instance.Relay.Info.Self = &self
|
instance.Relay.Info.Self = &self
|
||||||
instance.Relay.Info.PubKey = &owner
|
instance.Relay.Info.PubKey = &owner
|
||||||
instance.Relay.Info.Description = config.Info.Description
|
instance.Relay.Info.Description = config.Info.Description
|
||||||
instance.Relay.Info.Software = "https://github.com/coracle-social/zooid"
|
instance.Relay.Info.Software = "https://github.com/coracle-social/zooid"
|
||||||
instance.Relay.Info.Version = "v0.1.0"
|
instance.Relay.Info.Version = "v0.1.0"
|
||||||
instance.Relay.Info.SupportedNIPs = append(instance.Relay.Info.SupportedNIPs, 43)
|
instance.Relay.Info.SupportedNIPs = append(instance.Relay.Info.SupportedNIPs, "43")
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
|
|
||||||
@@ -91,8 +99,9 @@ func MakeInstance(filename string) (*Instance, error) {
|
|||||||
instance.Relay.OnEventSaved = instance.OnEventSaved
|
instance.Relay.OnEventSaved = instance.OnEventSaved
|
||||||
instance.Relay.OnEphemeralEvent = instance.OnEphemeralEvent
|
instance.Relay.OnEphemeralEvent = instance.OnEphemeralEvent
|
||||||
|
|
||||||
// Todo: when there's a new version of khatru
|
// Expiration
|
||||||
// instance.Relay.StartExpirationManager()
|
|
||||||
|
instance.Relay.StartExpirationManager(instance.Relay.QueryStored, instance.Relay.DeleteEvent)
|
||||||
|
|
||||||
// HTTP request handling
|
// HTTP request handling
|
||||||
|
|
||||||
@@ -124,6 +133,10 @@ func MakeInstance(filename string) (*Instance, error) {
|
|||||||
instance.Groups.Enable(instance)
|
instance.Groups.Enable(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.Push.Enabled {
|
||||||
|
instance.Push.Enable(instance)
|
||||||
|
}
|
||||||
|
|
||||||
// Update managed membership/admin lists
|
// Update managed membership/admin lists
|
||||||
|
|
||||||
instance.Management.AllowPubkey(config.GetSelf())
|
instance.Management.AllowPubkey(config.GetSelf())
|
||||||
@@ -141,14 +154,13 @@ func MakeInstance(filename string) (*Instance, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (instance *Instance) Cleanup() {
|
func (instance *Instance) Cleanup() {
|
||||||
|
instance.Relay.DisableExpirationManager()
|
||||||
instance.Events.Close()
|
instance.Events.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility methods
|
// Utility methods
|
||||||
|
|
||||||
func (instance *Instance) StripSignature(ctx context.Context, event nostr.Event) nostr.Event {
|
func (instance *Instance) StripSignature(pubkey nostr.PubKey, event nostr.Event) nostr.Event {
|
||||||
pubkey, _ := khatru.GetAuthed(ctx)
|
|
||||||
|
|
||||||
if instance.Config.Policy.StripSignatures && !instance.Config.CanManage(pubkey) {
|
if instance.Config.Policy.StripSignatures && !instance.Config.CanManage(pubkey) {
|
||||||
var zeroSig [64]byte
|
var zeroSig [64]byte
|
||||||
event.Sig = zeroSig
|
event.Sig = zeroSig
|
||||||
@@ -180,37 +192,6 @@ func (instance *Instance) AllowRecipientEvent(event nostr.Event) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (instance *Instance) IsInternalEvent(event nostr.Event) bool {
|
|
||||||
if event.Kind == nostr.KindApplicationSpecificData {
|
|
||||||
tag := event.Tags.Find("d")
|
|
||||||
|
|
||||||
if tag != nil && strings.HasPrefix(tag[1], "zooid/") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (instance *Instance) IsReadOnlyEvent(event nostr.Event) bool {
|
|
||||||
readOnlyEventKinds := []nostr.Kind{
|
|
||||||
RELAY_ADD_MEMBER,
|
|
||||||
RELAY_REMOVE_MEMBER,
|
|
||||||
RELAY_MEMBERS,
|
|
||||||
}
|
|
||||||
|
|
||||||
return slices.Contains(readOnlyEventKinds, event.Kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (instance *Instance) IsWriteOnlyEvent(event nostr.Event) bool {
|
|
||||||
writeOnlyEventKinds := []nostr.Kind{
|
|
||||||
RELAY_JOIN,
|
|
||||||
RELAY_LEAVE,
|
|
||||||
}
|
|
||||||
|
|
||||||
return slices.Contains(writeOnlyEventKinds, event.Kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (instance *Instance) GenerateInviteEvent(pubkey nostr.PubKey) nostr.Event {
|
func (instance *Instance) GenerateInviteEvent(pubkey nostr.PubKey) nostr.Event {
|
||||||
filter := nostr.Filter{
|
filter := nostr.Filter{
|
||||||
Kinds: []nostr.Kind{RELAY_INVITE},
|
Kinds: []nostr.Kind{RELAY_INVITE},
|
||||||
@@ -246,7 +227,7 @@ func (instance *Instance) OnConnect(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (instance *Instance) PreventBroadcast(ws *khatru.WebSocket, filter nostr.Filter, event nostr.Event) bool {
|
func (instance *Instance) PreventBroadcast(ws *khatru.WebSocket, filter nostr.Filter, event nostr.Event) bool {
|
||||||
return instance.IsWriteOnlyEvent(event)
|
return IsWriteOnlyEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (instance *Instance) StoreEvent(ctx context.Context, event nostr.Event) error {
|
func (instance *Instance) StoreEvent(ctx context.Context, event nostr.Event) error {
|
||||||
@@ -298,21 +279,17 @@ func (instance *Instance) QueryStored(ctx context.Context, filter nostr.Filter)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !yield(instance.StripSignature(ctx, event)) {
|
if !yield(instance.StripSignature(pubkey, event)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for event := range instance.Events.QueryEvents(filter, 1000) {
|
for event := range instance.Events.QueryEvents(filter, 1000) {
|
||||||
if event.Kind == RELAY_INVITE {
|
if !IsReadableEvent(event) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if instance.IsInternalEvent(event) {
|
if event.Kind == PUSH_SUBSCRIPTION && event.PubKey != pubkey {
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if instance.IsWriteOnlyEvent(event) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +297,7 @@ func (instance *Instance) QueryStored(ctx context.Context, filter nostr.Filter)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !yield(instance.StripSignature(ctx, event)) {
|
if !yield(instance.StripSignature(pubkey, event)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,15 +324,19 @@ func (instance *Instance) OnEvent(ctx context.Context, event nostr.Event) (rejec
|
|||||||
return instance.Management.ValidateJoinRequest(event)
|
return instance.Management.ValidateJoinRequest(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if event.Kind == PUSH_SUBSCRIPTION {
|
||||||
|
return instance.Push.ValidatePushSubscription(event)
|
||||||
|
}
|
||||||
|
|
||||||
if !instance.Management.IsMember(pubkey) {
|
if !instance.Management.IsMember(pubkey) {
|
||||||
return true, "restricted: you are not a member of this relay"
|
return true, "restricted: you are not a member of this relay"
|
||||||
}
|
}
|
||||||
|
|
||||||
if instance.IsInternalEvent(event) {
|
if IsInternalEvent(event) {
|
||||||
return true, "invalid: this event's kind is not accepted"
|
return true, "invalid: this event's kind is not accepted"
|
||||||
}
|
}
|
||||||
|
|
||||||
if instance.IsReadOnlyEvent(event) {
|
if IsReadOnlyEvent(event) {
|
||||||
return true, "invalid: this event's kind is not accepted"
|
return true, "invalid: this event's kind is not accepted"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,6 +387,10 @@ func (instance *Instance) OnEventSaved(ctx context.Context, event nostr.Event) {
|
|||||||
if event.Kind == nostr.KindSimpleGroupDeleteGroup {
|
if event.Kind == nostr.KindSimpleGroupDeleteGroup {
|
||||||
instance.Groups.DeleteGroup(h)
|
instance.Groups.DeleteGroup(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if instance.Config.Push.Enabled && !IsWriteOnlyEvent(event) {
|
||||||
|
instance.Push.HandleEvent(event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (instance *Instance) OnEphemeralEvent(ctx context.Context, event nostr.Event) {
|
func (instance *Instance) OnEphemeralEvent(ctx context.Context, event nostr.Event) {
|
||||||
@@ -416,4 +401,8 @@ func (instance *Instance) OnEphemeralEvent(ctx context.Context, event nostr.Even
|
|||||||
if event.Kind == RELAY_LEAVE {
|
if event.Kind == RELAY_LEAVE {
|
||||||
instance.Management.RemoveMember(event.PubKey)
|
instance.Management.RemoveMember(event.PubKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if instance.Config.Push.Enabled && !IsWriteOnlyEvent(event) {
|
||||||
|
instance.Push.HandleEvent(event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+245
@@ -0,0 +1,245 @@
|
|||||||
|
package zooid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"fiatjaf.com/nostr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Struct definition
|
||||||
|
|
||||||
|
type PushManager struct {
|
||||||
|
Config *Config
|
||||||
|
Events *EventStore
|
||||||
|
Management *ManagementStore
|
||||||
|
Groups *GroupStore
|
||||||
|
client *http.Client
|
||||||
|
errorCounts map[string]int // tracks consecutive errors per callback URL
|
||||||
|
errorCountMu sync.Mutex // protects errorCounts map
|
||||||
|
}
|
||||||
|
|
||||||
|
type PushPayload struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Relay string `json:"relay"`
|
||||||
|
Event *nostr.Event `json:"event,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handlers
|
||||||
|
|
||||||
|
func (p *PushManager) ValidatePushSubscription(event nostr.Event) (reject bool, msg string) {
|
||||||
|
if event.Tags.GetD() == "" {
|
||||||
|
return true, "invalid: missing or empty d tag"
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Tags.FindWithValue("relay", "wss://"+p.Config.Host+"/") == nil {
|
||||||
|
return true, "invalid: relay tag does not match this relay's URL"
|
||||||
|
}
|
||||||
|
|
||||||
|
filterTags := slices.Collect(event.Tags.FindAll("filter"))
|
||||||
|
if len(filterTags) == 0 {
|
||||||
|
return true, "invalid: at least one filter tag is required"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, filterTag := range filterTags {
|
||||||
|
if len(filterTag) < 2 {
|
||||||
|
return true, "invalid: filter tag is malformed"
|
||||||
|
}
|
||||||
|
|
||||||
|
var filter nostr.Filter
|
||||||
|
if err := json.Unmarshal([]byte(filterTag[1]), &filter); err != nil {
|
||||||
|
return true, "invalid: filter tag contains invalid JSON: " + err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ignoreTag := range event.Tags.FindAll("ignore") {
|
||||||
|
if len(ignoreTag) < 2 {
|
||||||
|
return true, "invalid: ignore tag is malformed"
|
||||||
|
}
|
||||||
|
|
||||||
|
var filter nostr.Filter
|
||||||
|
if err := json.Unmarshal([]byte(ignoreTag[1]), &filter); err != nil {
|
||||||
|
return true, "invalid: ignore tag contains invalid JSON: " + err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbackTags := slices.Collect(event.Tags.FindAll("callback"))
|
||||||
|
|
||||||
|
if len(callbackTags) < 1 {
|
||||||
|
return true, "invalid: missing callback tag"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(callbackTags) > 1 {
|
||||||
|
return true, "invalid: too many callback tags"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, callbackTag := range callbackTags {
|
||||||
|
if len(callbackTag) < 2 || callbackTag[1] == "" {
|
||||||
|
return true, "invalid: empty callback tag"
|
||||||
|
}
|
||||||
|
|
||||||
|
callbackURL := callbackTag[1]
|
||||||
|
if parsedURL, err := url.Parse(callbackURL); err != nil || (parsedURL.Scheme != "http" && parsedURL.Scheme != "https") {
|
||||||
|
return true, "invalid: callback must be a valid HTTP or HTTPS URL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := nostr.Filter{
|
||||||
|
Kinds: []nostr.Kind{PUSH_SUBSCRIPTION},
|
||||||
|
Authors: []nostr.PubKey{event.PubKey},
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := p.Events.CountEvents(filter)
|
||||||
|
if err != nil {
|
||||||
|
return true, "internal: failed to query database"
|
||||||
|
}
|
||||||
|
|
||||||
|
if count > 10 {
|
||||||
|
return true, "invalid: too many subscriptions registered"
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PushManager) HandleEvent(event nostr.Event) {
|
||||||
|
if !IsReadableEvent(event) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := nostr.Filter{
|
||||||
|
Kinds: []nostr.Kind{PUSH_SUBSCRIPTION},
|
||||||
|
}
|
||||||
|
|
||||||
|
for subscriptionEvent := range p.Events.QueryEvents(filter, 0) {
|
||||||
|
if event.PubKey == subscriptionEvent.PubKey {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Groups.IsGroupEvent(event) && !p.Groups.CanRead(subscriptionEvent.PubKey, event) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
filterTags := subscriptionEvent.Tags.FindAll("filter")
|
||||||
|
matched := false
|
||||||
|
for filterTag := range filterTags {
|
||||||
|
if len(filterTag) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var filter nostr.Filter
|
||||||
|
if err := json.Unmarshal([]byte(filterTag[1]), &filter); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Matches(event) {
|
||||||
|
matched = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !matched {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ignoreTags := subscriptionEvent.Tags.FindAll("ignore")
|
||||||
|
ignored := false
|
||||||
|
for ignoreTag := range ignoreTags {
|
||||||
|
if len(ignoreTag) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var ignore nostr.Filter
|
||||||
|
if err := json.Unmarshal([]byte(ignoreTag[1]), &ignore); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ignore.Matches(event) {
|
||||||
|
ignored = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ignored {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
callbackTag := subscriptionEvent.Tags.Find("callback")
|
||||||
|
|
||||||
|
if callbackTag == nil || len(callbackTag) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
callback := callbackTag[1]
|
||||||
|
|
||||||
|
payload := PushPayload{
|
||||||
|
ID: event.ID.Hex(),
|
||||||
|
Relay: "wss://" + p.Config.Host + "/",
|
||||||
|
}
|
||||||
|
|
||||||
|
if subscriptionEvent.Tags.Find("include_event") != nil {
|
||||||
|
payload.Event = &event
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadBytes, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go p.sendCallback(subscriptionEvent.ID, callback, payloadBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PushManager) sendCallback(subscriptionID nostr.ID, callback string, payloadBytes []byte) {
|
||||||
|
resp, err := p.client.Post(callback, "application/json", bytes.NewReader(payloadBytes))
|
||||||
|
if resp != nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementError := func() (count int) {
|
||||||
|
p.errorCountMu.Lock()
|
||||||
|
p.errorCounts[callback]++
|
||||||
|
count = p.errorCounts[callback]
|
||||||
|
p.errorCountMu.Unlock()
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
clearError := func() {
|
||||||
|
p.errorCountMu.Lock()
|
||||||
|
delete(p.errorCounts, callback)
|
||||||
|
p.errorCountMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && resp.StatusCode == 200 {
|
||||||
|
clearError()
|
||||||
|
} else if err == nil && resp.StatusCode == 404 {
|
||||||
|
log.Printf("Callback returned 404, deleting subscription %s", subscriptionID.Hex())
|
||||||
|
p.Events.DeleteEvent(subscriptionID)
|
||||||
|
clearError()
|
||||||
|
} else {
|
||||||
|
count := incrementError()
|
||||||
|
|
||||||
|
if count >= 10 {
|
||||||
|
log.Printf("Deleting subscription %s due to 10 consecutive failures", subscriptionID.Hex())
|
||||||
|
p.Events.DeleteEvent(subscriptionID)
|
||||||
|
clearError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middleware
|
||||||
|
|
||||||
|
func (p *PushManager) Enable(instance *Instance) {
|
||||||
|
p.client = &http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
p.errorCounts = make(map[string]int)
|
||||||
|
|
||||||
|
instance.Relay.Info.SupportedNIPs = append(instance.Relay.Info.SupportedNIPs, "9a")
|
||||||
|
}
|
||||||
@@ -14,10 +14,59 @@ const (
|
|||||||
RELAY_JOIN = 28934
|
RELAY_JOIN = 28934
|
||||||
RELAY_INVITE = 28935
|
RELAY_INVITE = 28935
|
||||||
RELAY_LEAVE = 28936
|
RELAY_LEAVE = 28936
|
||||||
|
PUSH_SUBSCRIPTION = 30390
|
||||||
BANNED_PUBKEYS = "zooid/banned_pubkeys"
|
BANNED_PUBKEYS = "zooid/banned_pubkeys"
|
||||||
BANNED_EVENTS = "zooid/banned_events"
|
BANNED_EVENTS = "zooid/banned_events"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func IsInternalEvent(event nostr.Event) bool {
|
||||||
|
if event.Kind == nostr.KindApplicationSpecificData {
|
||||||
|
tag := event.Tags.Find("d")
|
||||||
|
|
||||||
|
if tag != nil && strings.HasPrefix(tag[1], "zooid/") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsReadOnlyEvent(event nostr.Event) bool {
|
||||||
|
readOnlyEventKinds := []nostr.Kind{
|
||||||
|
RELAY_ADD_MEMBER,
|
||||||
|
RELAY_REMOVE_MEMBER,
|
||||||
|
RELAY_MEMBERS,
|
||||||
|
}
|
||||||
|
|
||||||
|
return slices.Contains(readOnlyEventKinds, event.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsWriteOnlyEvent(event nostr.Event) bool {
|
||||||
|
writeOnlyEventKinds := []nostr.Kind{
|
||||||
|
RELAY_JOIN,
|
||||||
|
RELAY_LEAVE,
|
||||||
|
PUSH_SUBSCRIPTION,
|
||||||
|
}
|
||||||
|
|
||||||
|
return slices.Contains(writeOnlyEventKinds, event.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsReadableEvent(event nostr.Event) bool {
|
||||||
|
if event.Kind == RELAY_INVITE {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsInternalEvent(event) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsWriteOnlyEvent(event) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func First[T any](s []T) T {
|
func First[T any](s []T) T {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
var zero T
|
var zero T
|
||||||
|
|||||||
Reference in New Issue
Block a user