style: using effective go and refactoring

This commit is contained in:
Kay 2023-08-21 20:17:25 +08:00 committed by fiatjaf_
parent abb66db97e
commit ac2350c722
17 changed files with 81 additions and 68 deletions

8
Makefile Normal file
View File

@ -0,0 +1,8 @@
### Tools needed for development
devtools:
@echo "Installing devtools"
go install mvdan.cc/gofumpt@latest
### Formatting, linting, and vetting
fmt:
gofumpt -l -w .

View File

@ -54,8 +54,8 @@ type Envelope interface {
} }
type EventEnvelope struct { type EventEnvelope struct {
SubscriptionID *string SubscriptionID *string `json:"subscription_id"`
Event Event `json:"event"`
} }
var ( var (
@ -266,9 +266,9 @@ func (v CloseEnvelope) MarshalJSON() ([]byte, error) {
} }
type OKEnvelope struct { type OKEnvelope struct {
EventID string EventID string `json:"event_id"`
OK bool OK bool `json:"ok"`
Reason *string Reason *string `json:"reason"`
} }
func (_ OKEnvelope) Label() string { return "OK" } func (_ OKEnvelope) Label() string { return "OK" }
@ -307,8 +307,8 @@ func (v OKEnvelope) MarshalJSON() ([]byte, error) {
} }
type AuthEnvelope struct { type AuthEnvelope struct {
Challenge *string Challenge *string `json:"challenge"`
Event Event Event Event `json:"event"`
} }
func (_ AuthEnvelope) Label() string { return "AUTH" } func (_ AuthEnvelope) Label() string { return "AUTH" }

View File

@ -58,13 +58,13 @@ const (
KindApplicationSpecificData int = 30078 KindApplicationSpecificData int = 30078
) )
// Event Stringer interface, just returns the raw JSON as a string // Event Stringer interface, just returns the raw JSON as a string.
func (evt Event) String() string { func (evt Event) String() string {
j, _ := easyjson.Marshal(evt) j, _ := easyjson.Marshal(evt)
return string(j) return string(j)
} }
// 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())
return hex.EncodeToString(h[:]) return hex.EncodeToString(h[:])
@ -128,7 +128,7 @@ func (evt Event) CheckSignature() (bool, error) {
return sig.Verify(hash[:], pubkey), nil return sig.Verify(hash[:], pubkey), nil
} }
// Sign signs an event with a given privateKey // Sign signs an event with a given privateKey.
func (evt *Event) Sign(privateKey string) error { func (evt *Event) Sign(privateKey string) error {
s, err := hex.DecodeString(privateKey) s, err := hex.DecodeString(privateKey)
if err != nil { if err != nil {

View File

@ -3,5 +3,4 @@
package nostr package nostr
func debugLogf(str string, args ...any) { func debugLogf(str string, args ...any) {
return
} }

View File

@ -40,7 +40,8 @@ func QueryIdentifier(ctx context.Context, fullname string) (*nostr.ProfilePointe
return nil, fmt.Errorf("hostname doesn't have a dot") return nil, fmt.Errorf("hostname doesn't have a dot")
} }
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://%s/.well-known/nostr.json?name=%s", domain, name), nil) req, err := http.NewRequestWithContext(ctx, "GET",
fmt.Sprintf("https://%s/.well-known/nostr.json?name=%s", domain, name), nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create a request: %w", err) return nil, fmt.Errorf("failed to create a request: %w", err)
} }
@ -54,6 +55,7 @@ func QueryIdentifier(ctx context.Context, fullname string) (*nostr.ProfilePointe
if err != nil { if err != nil {
return nil, fmt.Errorf("request failed: %w", err) return nil, fmt.Errorf("request failed: %w", err)
} }
defer res.Body.Close()
var result WellKnownResponse var result WellKnownResponse
if err := json.NewDecoder(res.Body).Decode(&result); err != nil { if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
@ -62,12 +64,12 @@ func QueryIdentifier(ctx context.Context, fullname string) (*nostr.ProfilePointe
pubkey, ok := result.Names[name] pubkey, ok := result.Names[name]
if !ok { if !ok {
return nil, nil return &nostr.ProfilePointer{}, nil
} }
if len(pubkey) == 64 { if len(pubkey) == 64 {
if _, err := hex.DecodeString(pubkey); err != nil { if _, err := hex.DecodeString(pubkey); err != nil {
return nil, nil return &nostr.ProfilePointer{}, nil
} }
} }

View File

@ -4,15 +4,12 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"time" "time"
) )
// Fetch fetches the NIP-11 RelayInformationDocument. // Fetch fetches the NIP-11 RelayInformationDocument.
func Fetch(ctx context.Context, u string) (info *RelayInformationDocument, err error) { func Fetch(ctx context.Context, u string) (info *RelayInformationDocument, err error) {
if _, ok := ctx.Deadline(); !ok { if _, ok := ctx.Deadline(); !ok {
@ -47,6 +44,7 @@ func Fetch(ctx context.Context, u string) (info *RelayInformationDocument, err e
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close()
info = &RelayInformationDocument{} info = &RelayInformationDocument{}
dec := json.NewDecoder(resp.Body) dec := json.NewDecoder(resp.Body)
err = dec.Decode(info) err = dec.Decode(info)

View File

@ -144,8 +144,8 @@ func EncodePublicKey(publicKeyHex string) (string, error) {
return bech32.Encode("npub", bits5) return bech32.Encode("npub", bits5)
} }
func EncodeNote(eventIdHex string) (string, error) { func EncodeNote(eventIDHex string) (string, error) {
b, err := hex.DecodeString(eventIdHex) b, err := hex.DecodeString(eventIDHex)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to decode event id hex: %w", err) return "", fmt.Errorf("failed to decode event id hex: %w", err)
} }
@ -178,11 +178,11 @@ func EncodeProfile(publicKeyHex string, relays []string) (string, error) {
return bech32.Encode("nprofile", bits5) return bech32.Encode("nprofile", bits5)
} }
func EncodeEvent(eventIdHex string, relays []string, author string) (string, error) { func EncodeEvent(eventIDHex string, relays []string, author string) (string, error) {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
id, err := hex.DecodeString(eventIdHex) id, err := hex.DecodeString(eventIDHex)
if err != nil || len(id) != 32 { if err != nil || len(id) != 32 {
return "", fmt.Errorf("invalid id '%s': %w", eventIdHex, err) return "", fmt.Errorf("invalid id '%s': %w", eventIDHex, err)
} }
writeTLVEntry(buf, TLVDefault, id) writeTLVEntry(buf, TLVDefault, id)

View File

@ -55,15 +55,14 @@ func CheckDelegation(ev *nostr.Event) (ok bool, err error) {
d := new(DelegationToken) d := new(DelegationToken)
if ok, err := d.Parse(ev); ok == true && (err == nil || err == NoDelegationTag) { if ok, err := d.Parse(ev); ok == true && (err == nil || err == NoDelegationTag) {
return true, nil return true, nil
} else {
return false, err
} }
return false, err
} }
// Import verifies that t is NIP-26 delegation token for the given delegatee. // Import verifies that t is NIP-26 delegation token for the given delegatee.
// The returned DelegationToken object can be used in DelegatedSign. // The returned DelegationToken object can be used in DelegatedSign.
// If the token signature verification fails, the error VerificationFailed will be returned. // If the token signature verification fails, the error VerificationFailed will be returned.
func Import(t nostr.Tag, delegatee_pk string) (d *DelegationToken, e error) { func Import(t nostr.Tag, delegateePk string) (d *DelegationToken, e error) {
d = new(DelegationToken) d = new(DelegationToken)
if len(t) == 4 && t[0] == "delegation" { if len(t) == 4 && t[0] == "delegation" {
copy(d.tag[:], t) copy(d.tag[:], t)
@ -81,16 +80,16 @@ func Import(t nostr.Tag, delegatee_pk string) (d *DelegationToken, e error) {
} }
// compute the digest // compute the digest
h := sha256.Sum256([]byte(fmt.Sprintf("nostr:delegation:%s:%s", delegatee_pk, d.tag[2]))) h := sha256.Sum256([]byte(fmt.Sprintf("nostr:delegation:%s:%s", delegateePk, d.tag[2])))
sig, err := schnorr.ParseSignature(d.token[:]) sig, err := schnorr.ParseSignature(d.token[:])
if err != nil { if err != nil {
return nil, fmt.Errorf("Error: %s", err.Error()) return nil, fmt.Errorf("error: %s", err.Error())
} }
pubkey, err := schnorr.ParsePubKey(d.delegator[:]) pubkey, err := schnorr.ParsePubKey(d.delegator[:])
if err != nil { if err != nil {
return nil, fmt.Errorf("Error: %s", err.Error()) return nil, fmt.Errorf("error: %s", err.Error())
} }
if !sig.Verify(h[:], pubkey) { if !sig.Verify(h[:], pubkey) {
return nil, VerificationFailed return nil, VerificationFailed
@ -131,15 +130,15 @@ jump1:
goto jump2 goto jump2
} }
} }
return false, fmt.Errorf("Event kind %d is not allowed in delegation conditions.", ev.Kind) return false, fmt.Errorf("event kind %d is not allowed in delegation condition", ev.Kind)
} }
jump2: jump2:
if d.since != nil && ev.CreatedAt.Time().Before(*d.since) { if d.since != nil && ev.CreatedAt.Time().Before(*d.since) {
return false, fmt.Errorf("Event is created before delegation conditions allow.") return false, fmt.Errorf("event is created before delegation conditions allow")
} }
if d.until != nil && ev.CreatedAt.Time().After(*d.until) { if d.until != nil && ev.CreatedAt.Time().After(*d.until) {
return false, fmt.Errorf("Event is created after delegation conditions allow.") return false, fmt.Errorf("event is created after delegation conditions allow")
} }
// compute the digest // compute the digest
@ -147,12 +146,12 @@ jump2:
sig, err := schnorr.ParseSignature(d.token[:]) sig, err := schnorr.ParseSignature(d.token[:])
if err != nil { if err != nil {
return false, fmt.Errorf("Error: %s", err.Error()) return false, fmt.Errorf("error: %s", err.Error())
} }
pubkey, err := schnorr.ParsePubKey(d.delegator[:]) pubkey, err := schnorr.ParsePubKey(d.delegator[:])
if err != nil { if err != nil {
return false, fmt.Errorf("Error: %s", err.Error()) return false, fmt.Errorf("error: %s", err.Error())
} }
return sig.Verify(h[:], pubkey), nil return sig.Verify(h[:], pubkey), nil
@ -160,14 +159,16 @@ jump2:
// DelegatedSign performs a delegated signature on the event ev. // DelegatedSign performs a delegated signature on the event ev.
// The delegation signature is not verified. If desired, the caller can ensure the delegation signature is correct by calling d.Parse(ev) or CheckDelegation(ev) afterwards. // The delegation signature is not verified. If desired, the caller can ensure the delegation signature is correct by calling d.Parse(ev) or CheckDelegation(ev) afterwards.
func DelegatedSign(ev *nostr.Event, d *DelegationToken, delegatee_sk string) error { func DelegatedSign(ev *nostr.Event,
d *DelegationToken, delegateeSk string,
) error {
for _, t := range ev.Tags { for _, t := range ev.Tags {
if t[0] == "delegation" { if t[0] == "delegation" {
return fmt.Errorf("event already has delegation token") return fmt.Errorf("event already has delegation token")
} }
} }
if d.since != nil && ev.CreatedAt.Time().Before(*d.since) || d.until != nil && ev.CreatedAt.Time().After(*d.until) { if d.since != nil && ev.CreatedAt.Time().Before(*d.since) || d.until != nil && ev.CreatedAt.Time().After(*d.until) {
return fmt.Errorf("event created_at field is not compatible with delegation token time conditions.") return fmt.Errorf("event created_at field is not compatible with delegation token time conditions")
} }
if len(d.kinds) > 0 { if len(d.kinds) > 0 {
for _, k := range d.kinds { for _, k := range d.kinds {
@ -175,34 +176,36 @@ func DelegatedSign(ev *nostr.Event, d *DelegationToken, delegatee_sk string) err
goto jump goto jump
} }
} }
return fmt.Errorf("event kind %d is not compatible with delegation token conditions.", ev.Kind) return fmt.Errorf("event kind %d is not compatible with delegation token conditions", ev.Kind)
} }
jump: jump:
if pk, e := nostr.GetPublicKey(delegatee_sk); e != nil { if pk, e := nostr.GetPublicKey(delegateeSk); e != nil {
return fmt.Errorf("invalid delegatee secret key.") return fmt.Errorf("invalid delegatee secret key")
} else { } else {
ev.PubKey = pk ev.PubKey = pk
} }
ev.Tags = append(ev.Tags, d.Tag()) ev.Tags = append(ev.Tags, d.Tag())
return ev.Sign(delegatee_sk) return ev.Sign(delegateeSk)
} }
// CreateToken creates a DelegationToken according to NIP-26. // CreateToken creates a DelegationToken according to NIP-26.
func CreateToken(delegator_sk string, delegatee_pk string, kinds []int, since *time.Time, until *time.Time) (d *DelegationToken, e error) { func CreateToken(delegator_sk string, delegatee_pk string, kinds []int,
since *time.Time, until *time.Time,
) (d *DelegationToken, e error) {
d = new(DelegationToken) d = new(DelegationToken)
s, e := hex.DecodeString(delegator_sk) s, e := hex.DecodeString(delegator_sk)
if e != nil { if e != nil {
return nil, fmt.Errorf("invalid delegator secret key") return nil, fmt.Errorf("invalid delegator secret key")
} }
tee_pk, e := hex.DecodeString(delegatee_pk) teePk, e := hex.DecodeString(delegatee_pk)
if len(tee_pk) != 32 || e != nil { if len(teePk) != 32 || e != nil {
return nil, fmt.Errorf("invalid delegatee pubkey") return nil, fmt.Errorf("invalid delegatee pubkey")
} }
// set delegator // set delegator
sk, tor_pk := btcec.PrivKeyFromBytes(s) sk, torPk := btcec.PrivKeyFromBytes(s)
copy(d.delegator[:], schnorr.SerializePubKey(tor_pk)) copy(d.delegator[:], schnorr.SerializePubKey(torPk))
d.kinds = kinds d.kinds = kinds
d.since = since d.since = since
@ -213,7 +216,7 @@ func CreateToken(delegator_sk string, delegatee_pk string, kinds []int, since *t
d.tag[1] = fmt.Sprintf("%x", d.delegator) d.tag[1] = fmt.Sprintf("%x", d.delegator)
d.tag[2] = d.Conditions() d.tag[2] = d.Conditions()
h := sha256.Sum256([]byte(fmt.Sprintf("nostr:delegation:%x:%s", tee_pk, d.tag[2]))) h := sha256.Sum256([]byte(fmt.Sprintf("nostr:delegation:%x:%s", teePk, d.tag[2])))
if sig, err := schnorr.Sign(sk, h[:]); err != nil { if sig, err := schnorr.Sign(sk, h[:]); err != nil {
panic(err) panic(err)

View File

@ -10,9 +10,9 @@ import (
func TestDelegateSign(t *testing.T) { func TestDelegateSign(t *testing.T) {
since := time.Unix(1600000000, 0) since := time.Unix(1600000000, 0)
until := time.Unix(1600000100, 0) until := time.Unix(1600000100, 0)
delegator_secret_key, delegatee_secret_key := "3f0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459da", "e9142f724955c5854de36324dab0434f97b15ec6b33464d56ebe491e3f559d1b" delegatorSecretKey, delegatee_secret_key := "3f0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459da", "e9142f724955c5854de36324dab0434f97b15ec6b33464d56ebe491e3f559d1b"
delegatee_pubkey, _ := nostr.GetPublicKey(delegatee_secret_key) delegateePubkey, _ := nostr.GetPublicKey(delegatee_secret_key)
d1, err := CreateToken(delegator_secret_key, delegatee_pubkey, []int{1, 2, 3}, &since, &until) d1, err := CreateToken(delegatorSecretKey, delegateePubkey, []int{1, 2, 3}, &since, &until)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -23,7 +23,7 @@ func TestDelegateSign(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
d2, err := Import(d1.Tag(), delegatee_pubkey) d2, err := Import(d1.Tag(), delegateePubkey)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -36,7 +36,7 @@ func TestDelegateSign(t *testing.T) {
} }
tag := []string{"delegation", "9ea72be3fcfe38103195a41b67b6f96c14ed92d2091d6d9eb8166a5c27b0c35d", "kind=1&kind=2&kind=3&created_at>1600000000", "8432b8c86f789c2783ef3becb0fabf4def6031c6a615fa7a622f31329d80ed1b2a79ab753c0462f1440503c94e1829158a3a854a1d418ad256ae2cf8aa19fa9a"} tag := []string{"delegation", "9ea72be3fcfe38103195a41b67b6f96c14ed92d2091d6d9eb8166a5c27b0c35d", "kind=1&kind=2&kind=3&created_at>1600000000", "8432b8c86f789c2783ef3becb0fabf4def6031c6a615fa7a622f31329d80ed1b2a79ab753c0462f1440503c94e1829158a3a854a1d418ad256ae2cf8aa19fa9a"}
d3, err := Import(tag, delegatee_pubkey) d3, err := Import(tag, delegateePubkey)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@ -23,8 +23,8 @@ func CreateUnsignedAuthEvent(challenge, pubkey, relayURL string) nostr.Event {
} }
} }
// helper function for ValidateAuthEvent // helper function for ValidateAuthEvent.
func parseUrl(input string) (*url.URL, error) { func parseURL(input string) (*url.URL, error) {
return url.Parse( return url.Parse(
strings.ToLower( strings.ToLower(
strings.TrimSuffix(input, "/"), strings.TrimSuffix(input, "/"),
@ -43,12 +43,12 @@ func ValidateAuthEvent(event *nostr.Event, challenge string, relayURL string) (p
return "", false return "", false
} }
expected, err := parseUrl(relayURL) expected, err := parseURL(relayURL)
if err != nil { if err != nil {
return "", false return "", false
} }
found, err := parseUrl(event.Tags.GetFirst([]string{"relay", ""}).Value()) found, err := parseURL(event.Tags.GetFirst([]string{"relay", ""}).Value())
if err != nil { if err != nil {
return "", false return "", false
} }

View File

@ -43,13 +43,13 @@ const (
NSON_MARKER_END = 317 // it's just the `,"nson":` (including ,": garbage to reduce false positives) part NSON_MARKER_END = 317 // it's just the `,"nson":` (including ,": garbage to reduce false positives) part
) )
var NotNSON = fmt.Errorf("not nson") var ErrNotNSON = fmt.Errorf("not nson")
func UnmarshalBytes(data []byte, evt *nostr.Event) (err error) { func UnmarshalBytes(data []byte, evt *nostr.Event) (err error) {
return Unmarshal(unsafe.String(unsafe.SliceData(data), len(data)), evt) return Unmarshal(unsafe.String(unsafe.SliceData(data), len(data)), evt)
} }
// Unmarshal turns a NSON string into a nostr.Event struct // Unmarshal turns a NSON string into a nostr.Event struct.
func Unmarshal(data string, evt *nostr.Event) (err error) { func Unmarshal(data string, evt *nostr.Event) (err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -59,7 +59,7 @@ func Unmarshal(data string, evt *nostr.Event) (err error) {
// check if it's nson // check if it's nson
if data[NSON_MARKER_START:NSON_MARKER_END] != ",\"nson\":" { if data[NSON_MARKER_START:NSON_MARKER_END] != ",\"nson\":" {
return NotNSON return ErrNotNSON
} }
// nson values // nson values
@ -168,7 +168,8 @@ func Marshal(evt *nostr.Event) (string, error) {
9 + tagBuilder.Len() + // tags and its label 9 + tagBuilder.Len() + // tags and its label
2, // the end 2, // the end
) )
base.WriteString(`{"id":"` + evt.ID + `","pubkey":"` + evt.PubKey + `","sig":"` + evt.Sig + `","created_at":` + strconv.FormatInt(int64(evt.CreatedAt), 10) + `,"nson":"`) base.WriteString(`{"id":"` + evt.ID + `","pubkey":"` + evt.PubKey + `","sig":"` + evt.Sig +
`","created_at":` + strconv.FormatInt(int64(evt.CreatedAt), 10) + `,"nson":"`)
nsonSizeBytes := len(nsonBuf) nsonSizeBytes := len(nsonBuf)
if nsonSizeBytes > 255 { if nsonSizeBytes > 255 {

View File

@ -72,7 +72,6 @@ func checkParsedCorrectly(t *testing.T, evt *nostr.Event, jevt string) (isBad bo
err := json.Unmarshal([]byte(jevt), &canonical) err := json.Unmarshal([]byte(jevt), &canonical)
if err != nil { if err != nil {
t.Fatalf("error unmarshaling normal json: %s", err) t.Fatalf("error unmarshaling normal json: %s", err)
return
} }
if evt.ID != canonical.ID { if evt.ID != canonical.ID {

View File

@ -23,7 +23,7 @@ const (
PublishStatusSucceeded Status = 1 PublishStatusSucceeded Status = 1
) )
var subscriptionIdCounter atomic.Int32 var subscriptionIDCounter atomic.Int32
func (s Status) String() string { func (s Status) String() string {
switch s { switch s {
@ -204,7 +204,6 @@ func (r *Relay) Connect(ctx context.Context) error {
go sub.Unsub() go sub.Unsub()
return true return true
}) })
return
}() }()
// queue all write operations here so we don't do mutex spaghetti // queue all write operations here so we don't do mutex spaghetti
@ -479,7 +478,7 @@ func (r *Relay) PrepareSubscription(ctx context.Context, filters Filters, opts .
panic(fmt.Errorf("must call .Connect() first before calling .Subscribe()")) panic(fmt.Errorf("must call .Connect() first before calling .Subscribe()"))
} }
current := subscriptionIdCounter.Add(1) current := subscriptionIDCounter.Add(1)
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
sub := &Subscription{ sub := &Subscription{

View File

@ -9,7 +9,7 @@ import (
"github.com/nbd-wtf/go-nostr/nip19" "github.com/nbd-wtf/go-nostr/nip19"
) )
// InputToProfile turns any npub/nprofile/hex/nip05 input into a ProfilePointer (or nil) // InputToProfile turns any npub/nprofile/hex/nip05 input into a ProfilePointer (or nil).
func InputToProfile(ctx context.Context, input string) *nostr.ProfilePointer { func InputToProfile(ctx context.Context, input string) *nostr.ProfilePointer {
// handle if it is a hex string // handle if it is a hex string
if len(input) == 64 { if len(input) == 64 {
@ -38,7 +38,7 @@ func InputToProfile(ctx context.Context, input string) *nostr.ProfilePointer {
return nil return nil
} }
// InputToEventPointer turns any note/nevent/hex input into a EventPointer (or nil) // InputToEventPointer turns any note/nevent/hex input into a EventPointer (or nil).
func InputToEventPointer(input string) *nostr.EventPointer { func InputToEventPointer(input string) *nostr.EventPointer {
// handle if it is a hex string // handle if it is a hex string
if len(input) == 64 { if len(input) == 64 {

View File

@ -33,7 +33,7 @@ func ParseReferences(evt *nostr.Event) []*Reference {
} }
if ref[6] == -1 { if ref[6] == -1 {
// didn't find a NIP-10 #[0] reference, so it's it's a NIP-27 mention // didn't find a NIP-10 #[0] reference, so it's a NIP-27 mention
nip19code := content[ref[2]:ref[3]] nip19code := content[ref[2]:ref[3]]
if prefix, data, err := nip19.Decode(nip19code); err == nil { if prefix, data, err := nip19.Decode(nip19code); err == nil {

View File

@ -117,7 +117,7 @@ func (sub *Subscription) Close() {
// Sub sets sub.Filters and then calls sub.Fire(ctx). // Sub sets sub.Filters and then calls sub.Fire(ctx).
// The subscription will be closed if the context expires. // The subscription will be closed if the context expires.
func (sub *Subscription) Sub(ctx context.Context, filters Filters) { func (sub *Subscription) Sub(_ context.Context, filters Filters) {
sub.Filters = filters sub.Filters = filters
sub.Fire() sub.Fire()
} }

View File

@ -12,10 +12,15 @@ type Tag []string
// StartsWith checks if a tag contains a prefix. // StartsWith checks if a tag contains a prefix.
// for example, // for example,
//
// ["p", "abcdef...", "wss://relay.com"] // ["p", "abcdef...", "wss://relay.com"]
//
// would match against // would match against
//
// ["p", "abcdef..."] // ["p", "abcdef..."]
//
// or even // or even
//
// ["p", "abcdef...", "wss://"] // ["p", "abcdef...", "wss://"]
func (tag Tag) StartsWith(prefix []string) bool { func (tag Tag) StartsWith(prefix []string) bool {
prefixLen := len(prefix) prefixLen := len(prefix)
@ -109,9 +114,8 @@ func (tags Tags) AppendUnique(tag Tag) Tags {
if tags.GetFirst(tag[:n]) == nil { if tags.GetFirst(tag[:n]) == nil {
return append(tags, tag) return append(tags, tag)
} else {
return tags
} }
return tags
} }
func (t *Tags) Scan(src any) error { func (t *Tags) Scan(src any) error {