mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-03-17 13:22:56 +01:00
nip29 event kinds.
This commit is contained in:
parent
21c6f34b51
commit
f06dd6b6fb
5
event.go
5
event.go
@ -59,8 +59,9 @@ const (
|
||||
KindArticle int = 30023
|
||||
KindApplicationSpecificData int = 30078
|
||||
KindSimpleChatMetadata int = 39000
|
||||
KindSimpleChatPermissions int = 39001
|
||||
KindSimpleChatSubGroups int = 39002
|
||||
KindSimpleChatMembers int = 39001
|
||||
KindSimpleChatRoles int = 39002
|
||||
KindSimpleChatSubGroups int = 39003
|
||||
)
|
||||
|
||||
// Event Stringer interface, just returns the raw JSON as a string
|
||||
|
194
nson/nson.go
Normal file
194
nson/nson.go
Normal file
@ -0,0 +1,194 @@
|
||||
package benchmarks
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
/*
|
||||
nson size
|
||||
kind chars
|
||||
content chars
|
||||
number of tags (let's say it's two)
|
||||
number of items on the first tag (let's say it's three)
|
||||
number of chars on the first item
|
||||
number of chars on the second item
|
||||
number of chars on the third item
|
||||
number of items on the second tag (let's say it's two)
|
||||
number of chars on the first item
|
||||
number of chars on the second item
|
||||
"nson":"xxkkccccttnn111122223333nn11112222"
|
||||
*/
|
||||
|
||||
func decodeNson(data string) (evt *nostr.Event, err error) {
|
||||
// defer func() {
|
||||
// if r := recover(); r != nil {
|
||||
// err = fmt.Errorf("failed to decode nson: %v", r)
|
||||
// }
|
||||
// }()
|
||||
|
||||
// check if it's nson
|
||||
if data[311:315] != "nson" {
|
||||
return nil, fmt.Errorf("not nson")
|
||||
}
|
||||
|
||||
// nson values
|
||||
nsonSizeBytes, _ := hex.DecodeString(data[318 : 318+2])
|
||||
nsonSize := int(nsonSizeBytes[0]) * 2 // number of bytes is given, we x2 because the string is in hex
|
||||
nsonDescriptors, _ := hex.DecodeString(data[320 : 320+nsonSize])
|
||||
|
||||
evt = &nostr.Event{}
|
||||
|
||||
// static fields
|
||||
evt.ID = data[7 : 7+64]
|
||||
evt.PubKey = data[83 : 83+64]
|
||||
evt.Sig = data[156 : 156+128]
|
||||
ts, _ := strconv.ParseInt(data[299:299+10], 10, 64)
|
||||
evt.CreatedAt = nostr.Timestamp(ts)
|
||||
|
||||
// dynamic fields
|
||||
// kind
|
||||
kindChars := int(nsonDescriptors[0])
|
||||
kindStart := 320 + nsonSize + 9 // len(`","kind":`)
|
||||
evt.Kind, _ = strconv.Atoi(data[kindStart : kindStart+kindChars])
|
||||
|
||||
// content
|
||||
contentChars := int(binary.BigEndian.Uint16(nsonDescriptors[1:3]))
|
||||
contentStart := kindStart + kindChars + 12 // len(`,"content":"`)
|
||||
evt.Content, _ = strconv.Unquote(data[contentStart-1 : contentStart+contentChars+1])
|
||||
|
||||
// tags
|
||||
nTags := int(nsonDescriptors[3])
|
||||
evt.Tags = make(nostr.Tags, nTags)
|
||||
tagsStart := contentStart + contentChars + 9 // len(`","tags":`)
|
||||
|
||||
nsonIndex := 3
|
||||
tagsIndex := tagsStart
|
||||
for t := 0; t < nTags; t++ {
|
||||
nsonIndex++
|
||||
tagsIndex += 1 // len(`[`) or len(`,`)
|
||||
nItems := int(nsonDescriptors[nsonIndex])
|
||||
tag := make(nostr.Tag, nItems)
|
||||
for n := 0; n < nItems; n++ {
|
||||
nsonIndex++
|
||||
itemStart := tagsIndex + 2 // len(`["`) or len(`,"`)
|
||||
itemChars := int(binary.BigEndian.Uint16(nsonDescriptors[nsonIndex:]))
|
||||
nsonIndex++
|
||||
tag[n], _ = strconv.Unquote(data[itemStart-1 : itemStart+itemChars+1])
|
||||
tagsIndex = itemStart + itemChars + 1 // len(`"`)
|
||||
}
|
||||
tagsIndex += 1 // len(`]`)
|
||||
evt.Tags[t] = tag
|
||||
}
|
||||
|
||||
return evt, err
|
||||
}
|
||||
|
||||
func encodeNson(evt *nostr.Event) (string, error) {
|
||||
// start building the nson descriptors (without the first byte that represents the nson size)
|
||||
nsonBuf := make([]byte, 256)
|
||||
|
||||
// build the tags
|
||||
nTags := len(evt.Tags)
|
||||
nsonBuf[3] = uint8(nTags)
|
||||
nsonIndex := 3 // start here
|
||||
|
||||
tagBuilder := strings.Builder{}
|
||||
tagBuilder.Grow(1000) // a guess
|
||||
tagBuilder.WriteString(`[`)
|
||||
for t, tag := range evt.Tags {
|
||||
nItems := len(tag)
|
||||
nsonIndex++
|
||||
nsonBuf[nsonIndex] = uint8(nItems)
|
||||
|
||||
tagBuilder.WriteString(`[`)
|
||||
for i, item := range tag {
|
||||
v := strconv.Quote(item)
|
||||
nsonIndex++
|
||||
binary.BigEndian.PutUint16(nsonBuf[nsonIndex:], uint16(len(v)-2))
|
||||
nsonIndex++
|
||||
tagBuilder.WriteString(v)
|
||||
if nItems > i+1 {
|
||||
tagBuilder.WriteString(`,`)
|
||||
}
|
||||
}
|
||||
tagBuilder.WriteString(`]`)
|
||||
if nTags > t+1 {
|
||||
tagBuilder.WriteString(`,`)
|
||||
}
|
||||
}
|
||||
tagBuilder.WriteString(`]}`)
|
||||
nsonBuf = nsonBuf[0 : nsonIndex+1]
|
||||
|
||||
kind := strconv.Itoa(evt.Kind)
|
||||
kindChars := len(kind)
|
||||
nsonBuf[0] = uint8(kindChars)
|
||||
|
||||
content := strconv.Quote(evt.Content)
|
||||
contentChars := len(content) - 2
|
||||
binary.BigEndian.PutUint16(nsonBuf[1:3], uint16(contentChars))
|
||||
|
||||
// actually build the json
|
||||
base := strings.Builder{}
|
||||
base.Grow(320 + // everything up to "nson":
|
||||
2 + len(nsonBuf)*2 + // nson
|
||||
9 + kindChars + // kind and its label
|
||||
12 + contentChars + // content and its label
|
||||
9 + tagBuilder.Len() + // tags and its label
|
||||
2, // the end
|
||||
)
|
||||
base.WriteString(`{"id":"` + evt.ID + `","pubkey":"` + evt.PubKey + `","sig":"` + evt.Sig + `","created_at":` + strconv.FormatInt(int64(evt.CreatedAt), 10) + `,"nson":"`)
|
||||
|
||||
nsonSizeBytes := len(nsonBuf)
|
||||
if nsonSizeBytes > 255 {
|
||||
return "", fmt.Errorf("can't encode to nson, there are too many tags or tag items")
|
||||
}
|
||||
base.WriteString(hex.EncodeToString([]byte{uint8(nsonSizeBytes)})) // nson size (bytes)
|
||||
|
||||
base.WriteString(hex.EncodeToString(nsonBuf)) // nson descriptors
|
||||
base.WriteString(`","kind":` + kind + `,"content":` + content + `,"tags":`)
|
||||
base.WriteString(tagBuilder.String() /* includes the end */)
|
||||
|
||||
return base.String(), nil
|
||||
}
|
||||
|
||||
// partial getters
|
||||
func nsonGetID(data string) string { return data[7 : 7+64] }
|
||||
func nsonGetPubkey(data string) string { return data[83 : 83+64] }
|
||||
func nsonGetSig(data string) string { return data[156 : 156+128] }
|
||||
func nsonGetCreatedAt(data string) nostr.Timestamp {
|
||||
ts, _ := strconv.ParseInt(data[299:299+10], 10, 64)
|
||||
return nostr.Timestamp(ts)
|
||||
}
|
||||
|
||||
func nsonGetKind(data string) int {
|
||||
nsonSizeBytes, _ := hex.DecodeString(data[318 : 318+2])
|
||||
nsonSize := int(nsonSizeBytes[0])
|
||||
nsonDescriptors, _ := hex.DecodeString(data[320 : 320+nsonSize])
|
||||
|
||||
kindChars := int(nsonDescriptors[0])
|
||||
kindStart := 320 + nsonSize + 9 // len(`","kind":`)
|
||||
kind, _ := strconv.Atoi(data[kindStart : kindStart+kindChars])
|
||||
|
||||
return kind
|
||||
}
|
||||
|
||||
func nsonGetContent(data string) string {
|
||||
nsonSizeBytes, _ := hex.DecodeString(data[318 : 318+2])
|
||||
nsonSize := int(nsonSizeBytes[0])
|
||||
nsonDescriptors, _ := hex.DecodeString(data[320 : 320+nsonSize])
|
||||
|
||||
kindChars := int(nsonDescriptors[0])
|
||||
kindStart := 320 + nsonSize + 9 // len(`","kind":`)
|
||||
|
||||
contentChars := int(binary.BigEndian.Uint16(nsonDescriptors[1:3]))
|
||||
contentStart := kindStart + kindChars + 12 // len(`,"content":"`)
|
||||
content, _ := strconv.Unquote(`"` + data[contentStart:contentStart+contentChars] + `"`)
|
||||
|
||||
return content
|
||||
}
|
126
nson/nson_test.go
Normal file
126
nson/nson_test.go
Normal file
@ -0,0 +1,126 @@
|
||||
package benchmarks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
var nsonTestEvents = []string{
|
||||
`{"id":"ae1fc7154296569d87ca4663f6bdf448c217d1590d28c85d158557b8b43b4d69","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","sig":"94e10947814b1ebe38af42300ecd90c7642763896c4f69506ae97bfdf54eec3c0c21df96b7d95daa74ff3d414b1d758ee95fc258125deebc31df0c6ba9396a51","created_at":1683660344,"nson":"1405000b0203000100400005040001004000000014","kind":30023,"content":"hello hello","tags":[["e","b6de44a9dd47d1c000f795ea0453046914f44ba7d5e369608b04867a575ea83e","reply"],["p","c26f7b252cea77a5b94f42b1a4771021be07d4df766407e47738605f7e3ab774","","wss://relay.damus.io"]]}`,
|
||||
`{"id":"ae1fc7154296569d87ca4663f6bdf448c217d1590d28c85d158557b8b43b4d69","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","sig":"94e10947814b1ebe38af42300ecd90c7642763896c4f69506ae97bfdf54eec3c0c21df96b7d95daa74ff3d414b1d758ee95fc258125deebc31df0c6ba9396a51","created_at":1683660344,"nson":"140500100203000100400005040001004000000014","kind":30023,"content":"hello\n\"hello\"","tags":[["e","b6de44a9dd47d1c000f795ea0453046914f44ba7d5e369608b04867a575ea83e","reply"],["p","c26f7b252cea77a5b94f42b1a4771021be07d4df766407e47738605f7e3ab774","","wss://relay.damus.io"]]}`,
|
||||
`{"id":"a235323ad6ae7032667330c4d52def2d6be67a973d71f2f1784b2b5b01d57026","pubkey":"69aeace80672c08ef7729a03e597ed4e9dd5ddaa7c457349d55d12c043e8a7ab","sig":"7e5fedc3c1c16abb95d207b73a689b5f17ab039cffd0b6bea62dcbfb607a27d38c830542df5ce762685a0da4e8edd28beab0c9a8f47e3037ff6e676ea6297bfa","created_at":1680277541,"nson":"0401049600","kind":1,"content":"Hello #Plebstrs 🤙\n\nExcited to share with you the latest improvements we've designed to enhance your user experience when creating new posts on #Plebstr.\n\nMain UX improvements include:\n— The ability to mention anyone directly in your new post 🔍\n— Real-time previews of all attachments while creating your post \U0001fa7b\n— Minimizing the new post window to enable #multitasking and easy access to your draft 📝\n\nThis is the first design mockup and we can't wait to bring it to you in an upcoming updates. Our amazing developers are currently working on it, together with other small improvements. Stay tuned! 🚀👨\u200d💻\n\n*Some details may change so keep your fingers crossed 😄🤞\n\n#comingsoon #maybe #insomeshapeorform\n\nhttps://nostr.build/i/nostr.build_2b337d24f0cd19eff0678893ac93d58ee374ca8c3b9215516aa76a05856ec9c0.png\nhttps://nostr.build/i/nostr.build_f79b14687a1f6e7275192554722a85be815219573d824e381c4913715644e10d.png\nhttps://nostr.build/i/nostr.build_009fa068a32383f88e19763fa22e16142cf44166cb097293f7082e7bf4a38eed.png\nhttps://nostr.build/i/nostr.build_47ecdd4867808b3f3af2620ac4bf40aefcc9022c4ba26762567012c22d636487.png","tags":[]}}`,
|
||||
}
|
||||
|
||||
func TestBasicNsonParse(t *testing.T) {
|
||||
for _, jevt := range nsonTestEvents {
|
||||
evt, _ := decodeNson(jevt)
|
||||
checkParsedCorrectly(t, evt, jevt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNsonPartialGet(t *testing.T) {
|
||||
for _, jevt := range nsonTestEvents {
|
||||
evt, _ := decodeNson(jevt)
|
||||
|
||||
if id := nsonGetID(jevt); id != evt.ID {
|
||||
t.Error("partial id wrong")
|
||||
}
|
||||
if pubkey := nsonGetPubkey(jevt); pubkey != evt.PubKey {
|
||||
t.Error("partial pubkey wrong")
|
||||
}
|
||||
if sig := nsonGetSig(jevt); sig != evt.Sig {
|
||||
t.Error("partial sig wrong")
|
||||
}
|
||||
if createdAt := nsonGetCreatedAt(jevt); createdAt != evt.CreatedAt {
|
||||
t.Error("partial created_at wrong")
|
||||
}
|
||||
if kind := nsonGetKind(jevt); kind != evt.Kind {
|
||||
t.Error("partial kind wrong")
|
||||
}
|
||||
if content := nsonGetContent(jevt); content != evt.Content {
|
||||
t.Error("partial content wrong")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeNson(t *testing.T) {
|
||||
jevt := `{
|
||||
"content": "hello world",
|
||||
"created_at": 1683762317,
|
||||
"id": "57ff66490a6a2af3992accc26ae95f3f60c6e5f84ed0ddf6f59c534d3920d3d2",
|
||||
"kind": 1,
|
||||
"pubkey": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||
"sig": "504d142aed7fa7e0f6dab5bcd7eed63963b0277a8e11bbcb03b94531beb4b95a12f1438668b02746bd5362161bc782068e6b71494060975414e793f9e19f57ea",
|
||||
"tags": [
|
||||
[
|
||||
"e",
|
||||
"b6de44a9dd47d1c000f795ea0453046914f44ba7d5e369608b04867a575ea83e",
|
||||
"reply"
|
||||
],
|
||||
[
|
||||
"p",
|
||||
"c26f7b252cea77a5b94f42b1a4771021be07d4df766407e47738605f7e3ab774",
|
||||
"",
|
||||
"wss://relay.damus.io"
|
||||
]
|
||||
]
|
||||
}`
|
||||
|
||||
evt := &nostr.Event{}
|
||||
json.Unmarshal([]byte(jevt), evt)
|
||||
|
||||
nevt, _ := encodeNson(evt)
|
||||
fmt.Println(nevt)
|
||||
}
|
||||
|
||||
func checkParsedCorrectly(t *testing.T, evt *nostr.Event, jevt string) (isBad bool) {
|
||||
var canonical nostr.Event
|
||||
err := json.Unmarshal([]byte(jevt), &canonical)
|
||||
fmt.Println(err)
|
||||
|
||||
if evt.ID != canonical.ID {
|
||||
t.Errorf("id is wrong: %s != %s", evt.ID, canonical.ID)
|
||||
isBad = true
|
||||
}
|
||||
if evt.PubKey != canonical.PubKey {
|
||||
t.Errorf("pubkey is wrong: %s != %s", evt.PubKey, canonical.PubKey)
|
||||
isBad = true
|
||||
}
|
||||
if evt.Sig != canonical.Sig {
|
||||
t.Errorf("sig is wrong: %s != %s", evt.Sig, canonical.Sig)
|
||||
isBad = true
|
||||
}
|
||||
if evt.Content != canonical.Content {
|
||||
t.Errorf("content is wrong: %s != %s", evt.Content, canonical.Content)
|
||||
isBad = true
|
||||
}
|
||||
if evt.Kind != canonical.Kind {
|
||||
t.Errorf("kind is wrong: %d != %d", evt.Kind, canonical.Kind)
|
||||
isBad = true
|
||||
}
|
||||
if evt.CreatedAt != nostr.Timestamp(canonical.CreatedAt) {
|
||||
t.Errorf("created_at is wrong: %v != %v", evt.CreatedAt, canonical.CreatedAt)
|
||||
isBad = true
|
||||
}
|
||||
if len(evt.Tags) != len(canonical.Tags) {
|
||||
t.Errorf("tag number is wrong: %v != %v", len(evt.Tags), len(canonical.Tags))
|
||||
isBad = true
|
||||
}
|
||||
for i := range evt.Tags {
|
||||
if len(evt.Tags[i]) != len(canonical.Tags[i]) {
|
||||
t.Errorf("tag[%d] length is wrong: `%v` != `%v`", i, len(evt.Tags[i]), len(canonical.Tags[i]))
|
||||
isBad = true
|
||||
}
|
||||
for j := range evt.Tags[i] {
|
||||
if evt.Tags[i][j] != canonical.Tags[i][j] {
|
||||
t.Errorf("tag[%d][%d] is wrong: `%s` != `%s`", i, j, evt.Tags[i][j], canonical.Tags[i][j])
|
||||
isBad = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isBad
|
||||
}
|
22
relay.go
22
relay.go
@ -50,7 +50,7 @@ type Relay struct {
|
||||
|
||||
challenges chan string // NIP-42 challenges
|
||||
notices chan string // NIP-01 NOTICEs
|
||||
okCallbacks *xsync.MapOf[string, func(bool, string)]
|
||||
okCallbacks *xsync.MapOf[string, func(bool, *string)]
|
||||
writeQueue chan writeRequest
|
||||
subscriptionChannelCloseQueue chan *Subscription
|
||||
|
||||
@ -72,7 +72,7 @@ func NewRelay(ctx context.Context, url string, opts ...RelayOption) *Relay {
|
||||
connectionContext: ctx,
|
||||
connectionContextCancel: cancel,
|
||||
Subscriptions: xsync.NewMapOf[*Subscription](),
|
||||
okCallbacks: xsync.NewMapOf[func(bool, string)](),
|
||||
okCallbacks: xsync.NewMapOf[func(bool, *string)](),
|
||||
writeQueue: make(chan writeRequest),
|
||||
subscriptionChannelCloseQueue: make(chan *Subscription),
|
||||
}
|
||||
@ -301,7 +301,7 @@ func (r *Relay) Connect(ctx context.Context) error {
|
||||
}
|
||||
case *OKEnvelope:
|
||||
if okCallback, exist := r.okCallbacks.Load(env.EventID); exist {
|
||||
okCallback(env.OK, *env.Reason)
|
||||
okCallback(env.OK, env.Reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -339,14 +339,18 @@ func (r *Relay) Publish(ctx context.Context, event Event) (Status, error) {
|
||||
defer cancel()
|
||||
|
||||
// listen for an OK callback
|
||||
okCallback := func(ok bool, msg string) {
|
||||
okCallback := func(ok bool, msg *string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if ok {
|
||||
status = PublishStatusSucceeded
|
||||
} else {
|
||||
status = PublishStatusFailed
|
||||
err = fmt.Errorf("msg: %s", msg)
|
||||
reason := ""
|
||||
if msg != nil {
|
||||
reason = *msg
|
||||
}
|
||||
err = fmt.Errorf("msg: %s", reason)
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
@ -399,13 +403,17 @@ func (r *Relay) Auth(ctx context.Context, event Event) (Status, error) {
|
||||
defer cancel()
|
||||
|
||||
// listen for an OK callback
|
||||
okCallback := func(ok bool, msg string) {
|
||||
okCallback := func(ok bool, msg *string) {
|
||||
mu.Lock()
|
||||
if ok {
|
||||
status = PublishStatusSucceeded
|
||||
} else {
|
||||
status = PublishStatusFailed
|
||||
err = fmt.Errorf("msg: %s", msg)
|
||||
reason := ""
|
||||
if msg != nil {
|
||||
reason = *msg
|
||||
}
|
||||
err = fmt.Errorf("msg: %s", reason)
|
||||
}
|
||||
mu.Unlock()
|
||||
cancel()
|
||||
|
Loading…
x
Reference in New Issue
Block a user