mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-06-04 20:19:35 +02:00
use fastjson to serialize and unmarshal/marshal events, remove need for special Time type.
This commit is contained in:
parent
41955a0601
commit
9721ffa851
77
aux.go
Normal file
77
aux.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package nostr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StringList []string
|
||||||
|
type IntList []int
|
||||||
|
|
||||||
|
func (as StringList) Equals(bs StringList) bool {
|
||||||
|
if len(as) != len(bs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range as {
|
||||||
|
for _, b := range bs {
|
||||||
|
if b == a {
|
||||||
|
goto next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// didn't find a B that corresponded to the current A
|
||||||
|
return false
|
||||||
|
|
||||||
|
next:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as IntList) Equals(bs IntList) bool {
|
||||||
|
if len(as) != len(bs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range as {
|
||||||
|
for _, b := range bs {
|
||||||
|
if b == a {
|
||||||
|
goto next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// didn't find a B that corresponded to the current A
|
||||||
|
return false
|
||||||
|
|
||||||
|
next:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (haystack StringList) Contains(needle string) bool {
|
||||||
|
for _, hay := range haystack {
|
||||||
|
if hay == needle {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (haystack StringList) ContainsPrefixOf(needle string) bool {
|
||||||
|
for _, hay := range haystack {
|
||||||
|
if strings.HasPrefix(needle, hay) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (haystack IntList) Contains(needle int) bool {
|
||||||
|
for _, hay := range haystack {
|
||||||
|
if hay == needle {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
84
event.go
84
event.go
@ -1,18 +1,26 @@
|
|||||||
package nostr
|
package nostr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fiatjaf/bip340"
|
"github.com/fiatjaf/bip340"
|
||||||
|
"github.com/valyala/fastjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
ID string
|
||||||
|
PubKey string
|
||||||
|
CreatedAt time.Time
|
||||||
|
Kind int
|
||||||
|
Tags Tags
|
||||||
|
Content string
|
||||||
|
Sig string
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
KindSetMetadata int = 0
|
KindSetMetadata int = 0
|
||||||
KindTextNote int = 1
|
KindTextNote int = 1
|
||||||
@ -22,36 +30,6 @@ const (
|
|||||||
KindDeletion int = 5
|
KindDeletion int = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
type Event struct {
|
|
||||||
ID string `json:"id"` // it's the hash of the serialized event
|
|
||||||
|
|
||||||
PubKey string `json:"pubkey"`
|
|
||||||
CreatedAt Time `json:"created_at"`
|
|
||||||
|
|
||||||
Kind int `json:"kind"`
|
|
||||||
|
|
||||||
Tags Tags `json:"tags"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Sig string `json:"sig"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Time time.Time
|
|
||||||
|
|
||||||
func (tm *Time) UnmarshalJSON(payload []byte) error {
|
|
||||||
unix, err := strconv.ParseInt(string(payload), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("time must be a unix timestamp as an integer, not '%s': %w",
|
|
||||||
string(payload), err)
|
|
||||||
}
|
|
||||||
t := Time(time.Unix(unix, 0))
|
|
||||||
*tm = t
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Time) MarshalJSON() ([]byte, error) {
|
|
||||||
return []byte(strconv.FormatInt(time.Time(t).Unix(), 10)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetID serializes and returns the event ID as a string
|
// GetID serializes and returns the event ID as a string
|
||||||
func (evt *Event) GetID() string {
|
func (evt *Event) GetID() string {
|
||||||
h := sha256.Sum256(evt.Serialize())
|
h := sha256.Sum256(evt.Serialize())
|
||||||
@ -62,36 +40,29 @@ func (evt *Event) GetID() string {
|
|||||||
func (evt *Event) Serialize() []byte {
|
func (evt *Event) Serialize() []byte {
|
||||||
// the serialization process is just putting everything into a JSON array
|
// the serialization process is just putting everything into a JSON array
|
||||||
// so the order is kept
|
// so the order is kept
|
||||||
arr := make([]interface{}, 6)
|
var arena fastjson.Arena
|
||||||
|
|
||||||
|
arr := arena.NewArray()
|
||||||
|
|
||||||
// version: 0
|
// version: 0
|
||||||
arr[0] = 0
|
arr.SetArrayItem(0, arena.NewNumberInt(0))
|
||||||
|
|
||||||
// pubkey
|
// pubkey
|
||||||
arr[1] = evt.PubKey
|
arr.SetArrayItem(1, arena.NewString(evt.PubKey))
|
||||||
|
|
||||||
// created_at
|
// created_at
|
||||||
arr[2] = int64(time.Time(evt.CreatedAt).Unix())
|
arr.SetArrayItem(2, arena.NewNumberInt(int(evt.CreatedAt.Unix())))
|
||||||
|
|
||||||
// kind
|
// kind
|
||||||
arr[3] = int64(evt.Kind)
|
arr.SetArrayItem(3, arena.NewNumberInt(evt.Kind))
|
||||||
|
|
||||||
// tags
|
// tags
|
||||||
if evt.Tags != nil {
|
arr.SetArrayItem(4, tagsToFastjsonArray(&arena, evt.Tags))
|
||||||
arr[4] = evt.Tags
|
|
||||||
} else {
|
|
||||||
arr[4] = make([]bool, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// content
|
// content
|
||||||
arr[5] = evt.Content
|
arr.SetArrayItem(5, arena.NewString(evt.Content))
|
||||||
|
|
||||||
serialized := new(bytes.Buffer)
|
return arr.MarshalTo(nil)
|
||||||
|
|
||||||
enc := json.NewEncoder(serialized)
|
|
||||||
enc.SetEscapeHTML(false)
|
|
||||||
_ = enc.Encode(arr)
|
|
||||||
return serialized.Bytes()[:serialized.Len()-1] // Encode add new line char
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckSignature checks if the signature is valid for the id
|
// CheckSignature checks if the signature is valid for the id
|
||||||
@ -104,19 +75,6 @@ func (evt Event) CheckSignature() (bool, error) {
|
|||||||
return false, fmt.Errorf("Event has invalid pubkey '%s': %w", evt.PubKey, err)
|
return false, fmt.Errorf("Event has invalid pubkey '%s': %w", evt.PubKey, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check tags
|
|
||||||
for _, tag := range evt.Tags {
|
|
||||||
for _, item := range tag {
|
|
||||||
switch item.(type) {
|
|
||||||
case string, int64, float64, int, bool:
|
|
||||||
// fine
|
|
||||||
default:
|
|
||||||
// not fine
|
|
||||||
return false, fmt.Errorf("tag contains an invalid value %v", item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := hex.DecodeString(evt.Sig)
|
s, err := hex.DecodeString(evt.Sig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("signature is invalid hex: %w", err)
|
return false, fmt.Errorf("signature is invalid hex: %w", err)
|
||||||
|
166
event_aux.go
Normal file
166
event_aux.go
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
package nostr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/valyala/fastjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tags []StringList
|
||||||
|
|
||||||
|
func (t *Tags) Scan(src interface{}) error {
|
||||||
|
var jtags []byte = make([]byte, 0)
|
||||||
|
|
||||||
|
switch v := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
jtags = v
|
||||||
|
case string:
|
||||||
|
jtags = []byte(v)
|
||||||
|
default:
|
||||||
|
return errors.New("couldn't scan tags, it's not a json string")
|
||||||
|
}
|
||||||
|
|
||||||
|
json.Unmarshal(jtags, &t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tags Tags) ContainsAny(tagName string, values StringList) bool {
|
||||||
|
for _, tag := range tags {
|
||||||
|
if len(tag) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tag[0] != tagName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if values.Contains(tag[1]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (evt *Event) UnmarshalJSON(payload []byte) error {
|
||||||
|
var fastjsonParser fastjson.Parser
|
||||||
|
parsed, err := fastjsonParser.ParseBytes(payload)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, err := parsed.Object()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("event is not an object")
|
||||||
|
}
|
||||||
|
|
||||||
|
var visiterr error
|
||||||
|
obj.Visit(func(k []byte, v *fastjson.Value) {
|
||||||
|
key := string(k)
|
||||||
|
switch key {
|
||||||
|
case "id":
|
||||||
|
id, err := v.StringBytes()
|
||||||
|
if err != nil {
|
||||||
|
visiterr = fmt.Errorf("invalid 'id' field: %w", err)
|
||||||
|
}
|
||||||
|
evt.ID = string(id)
|
||||||
|
case "pubkey":
|
||||||
|
id, err := v.StringBytes()
|
||||||
|
if err != nil {
|
||||||
|
visiterr = fmt.Errorf("invalid 'pubkey' field: %w", err)
|
||||||
|
}
|
||||||
|
evt.PubKey = string(id)
|
||||||
|
case "created_at":
|
||||||
|
val, err := v.Int64()
|
||||||
|
if err != nil {
|
||||||
|
visiterr = fmt.Errorf("invalid 'created_at' field: %w", err)
|
||||||
|
}
|
||||||
|
evt.CreatedAt = time.Unix(val, 0)
|
||||||
|
case "kind":
|
||||||
|
kind, err := v.Int64()
|
||||||
|
if err != nil {
|
||||||
|
visiterr = fmt.Errorf("invalid 'kind' field: %w", err)
|
||||||
|
}
|
||||||
|
evt.Kind = int(kind)
|
||||||
|
case "tags":
|
||||||
|
evt.Tags, err = fastjsonArrayToTags(v)
|
||||||
|
if err != nil {
|
||||||
|
visiterr = fmt.Errorf("invalid '%s' field: %w", key, err)
|
||||||
|
}
|
||||||
|
case "content":
|
||||||
|
id, err := v.StringBytes()
|
||||||
|
if err != nil {
|
||||||
|
visiterr = fmt.Errorf("invalid 'content' field: %w", err)
|
||||||
|
}
|
||||||
|
evt.Content = string(id)
|
||||||
|
case "sig":
|
||||||
|
id, err := v.StringBytes()
|
||||||
|
if err != nil {
|
||||||
|
visiterr = fmt.Errorf("invalid 'sig' field: %w", err)
|
||||||
|
}
|
||||||
|
evt.Sig = string(id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if visiterr != nil {
|
||||||
|
return visiterr
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (evt Event) MarshalJSON() ([]byte, error) {
|
||||||
|
var arena fastjson.Arena
|
||||||
|
|
||||||
|
o := arena.NewObject()
|
||||||
|
o.Set("id", arena.NewString(evt.ID))
|
||||||
|
o.Set("pubkey", arena.NewString(evt.PubKey))
|
||||||
|
o.Set("created_at", arena.NewNumberInt(int(evt.CreatedAt.Unix())))
|
||||||
|
o.Set("kind", arena.NewNumberInt(evt.Kind))
|
||||||
|
o.Set("tags", tagsToFastjsonArray(&arena, evt.Tags))
|
||||||
|
o.Set("content", arena.NewString(evt.Content))
|
||||||
|
o.Set("sig", arena.NewString(evt.Sig))
|
||||||
|
|
||||||
|
return o.MarshalTo(nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fastjsonArrayToTags(v *fastjson.Value) (Tags, error) {
|
||||||
|
arr, err := v.Array()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sll := make([]StringList, len(arr))
|
||||||
|
for i, v := range arr {
|
||||||
|
subarr, err := v.Array()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sl := make(StringList, len(arr))
|
||||||
|
for j, subv := range subarr {
|
||||||
|
sb, err := subv.StringBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sl[j] = string(sb)
|
||||||
|
}
|
||||||
|
sll[i] = sl
|
||||||
|
}
|
||||||
|
|
||||||
|
return Tags(sll), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagsToFastjsonArray(arena *fastjson.Arena, tags Tags) *fastjson.Value {
|
||||||
|
jtags := arena.NewArray()
|
||||||
|
for i, v := range tags {
|
||||||
|
arr := arena.NewArray()
|
||||||
|
for j, subv := range v {
|
||||||
|
arr.SetArrayItem(j, arena.NewString(subv))
|
||||||
|
}
|
||||||
|
jtags.SetArrayItem(i, arr)
|
||||||
|
}
|
||||||
|
return jtags
|
||||||
|
}
|
@ -8,78 +8,6 @@ import (
|
|||||||
"github.com/valyala/fastjson"
|
"github.com/valyala/fastjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StringList []string
|
|
||||||
type IntList []int
|
|
||||||
|
|
||||||
func (as StringList) Equals(bs StringList) bool {
|
|
||||||
if len(as) != len(bs) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, a := range as {
|
|
||||||
for _, b := range bs {
|
|
||||||
if b == a {
|
|
||||||
goto next
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// didn't find a B that corresponded to the current A
|
|
||||||
return false
|
|
||||||
|
|
||||||
next:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (as IntList) Equals(bs IntList) bool {
|
|
||||||
if len(as) != len(bs) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, a := range as {
|
|
||||||
for _, b := range bs {
|
|
||||||
if b == a {
|
|
||||||
goto next
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// didn't find a B that corresponded to the current A
|
|
||||||
return false
|
|
||||||
|
|
||||||
next:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (haystack StringList) Contains(needle string) bool {
|
|
||||||
for _, hay := range haystack {
|
|
||||||
if hay == needle {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (haystack StringList) ContainsPrefixOf(needle string) bool {
|
|
||||||
for _, hay := range haystack {
|
|
||||||
if strings.HasPrefix(needle, hay) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (haystack IntList) Contains(needle int) bool {
|
|
||||||
for _, hay := range haystack {
|
|
||||||
if hay == needle {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Filter) UnmarshalJSON(payload []byte) error {
|
func (f *Filter) UnmarshalJSON(payload []byte) error {
|
||||||
var fastjsonParser fastjson.Parser
|
var fastjsonParser fastjson.Parser
|
||||||
parsed, err := fastjsonParser.ParseBytes(payload)
|
parsed, err := fastjsonParser.ParseBytes(payload)
|
||||||
|
49
tags.go
49
tags.go
@ -1,49 +0,0 @@
|
|||||||
package nostr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Tags []Tag
|
|
||||||
type Tag []interface{}
|
|
||||||
|
|
||||||
func (t *Tags) Scan(src interface{}) error {
|
|
||||||
var jtags []byte = make([]byte, 0)
|
|
||||||
|
|
||||||
switch v := src.(type) {
|
|
||||||
case []byte:
|
|
||||||
jtags = v
|
|
||||||
case string:
|
|
||||||
jtags = []byte(v)
|
|
||||||
default:
|
|
||||||
return errors.New("couldn't scan tags, it's not a json string")
|
|
||||||
}
|
|
||||||
|
|
||||||
json.Unmarshal(jtags, &t)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tags Tags) ContainsAny(tagName string, values StringList) bool {
|
|
||||||
for _, tag := range tags {
|
|
||||||
if len(tag) < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTagName, ok := tag[0].(string)
|
|
||||||
if !ok || currentTagName != tagName {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTagValue, ok := tag[1].(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if values.Contains(currentTagValue) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user