mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-05-02 06:40:13 +02:00
style: using effective go and refactoring
This commit is contained in:
parent
abb66db97e
commit
ac2350c722
8
Makefile
Normal file
8
Makefile
Normal 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 .
|
14
envelopes.go
14
envelopes.go
@ -54,8 +54,8 @@ type Envelope interface {
|
||||
}
|
||||
|
||||
type EventEnvelope struct {
|
||||
SubscriptionID *string
|
||||
Event
|
||||
SubscriptionID *string `json:"subscription_id"`
|
||||
Event `json:"event"`
|
||||
}
|
||||
|
||||
var (
|
||||
@ -266,9 +266,9 @@ func (v CloseEnvelope) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
type OKEnvelope struct {
|
||||
EventID string
|
||||
OK bool
|
||||
Reason *string
|
||||
EventID string `json:"event_id"`
|
||||
OK bool `json:"ok"`
|
||||
Reason *string `json:"reason"`
|
||||
}
|
||||
|
||||
func (_ OKEnvelope) Label() string { return "OK" }
|
||||
@ -307,8 +307,8 @@ func (v OKEnvelope) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
type AuthEnvelope struct {
|
||||
Challenge *string
|
||||
Event Event
|
||||
Challenge *string `json:"challenge"`
|
||||
Event Event `json:"event"`
|
||||
}
|
||||
|
||||
func (_ AuthEnvelope) Label() string { return "AUTH" }
|
||||
|
6
event.go
6
event.go
@ -58,13 +58,13 @@ const (
|
||||
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 {
|
||||
j, _ := easyjson.Marshal(evt)
|
||||
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 {
|
||||
h := sha256.Sum256(evt.Serialize())
|
||||
return hex.EncodeToString(h[:])
|
||||
@ -128,7 +128,7 @@ func (evt Event) CheckSignature() (bool, error) {
|
||||
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 {
|
||||
s, err := hex.DecodeString(privateKey)
|
||||
if err != nil {
|
||||
|
@ -3,5 +3,4 @@
|
||||
package nostr
|
||||
|
||||
func debugLogf(str string, args ...any) {
|
||||
return
|
||||
}
|
||||
|
@ -40,7 +40,8 @@ func QueryIdentifier(ctx context.Context, fullname string) (*nostr.ProfilePointe
|
||||
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 {
|
||||
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 {
|
||||
return nil, fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
var result WellKnownResponse
|
||||
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]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
return &nostr.ProfilePointer{}, nil
|
||||
}
|
||||
|
||||
if len(pubkey) == 64 {
|
||||
if _, err := hex.DecodeString(pubkey); err != nil {
|
||||
return nil, nil
|
||||
return &nostr.ProfilePointer{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,15 +4,12 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
// Fetch fetches the NIP-11 RelayInformationDocument.
|
||||
func Fetch(ctx context.Context, u string) (info *RelayInformationDocument, err error) {
|
||||
if _, ok := ctx.Deadline(); !ok {
|
||||
@ -47,6 +44,7 @@ func Fetch(ctx context.Context, u string) (info *RelayInformationDocument, err e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
info = &RelayInformationDocument{}
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
err = dec.Decode(info)
|
||||
|
@ -144,8 +144,8 @@ func EncodePublicKey(publicKeyHex string) (string, error) {
|
||||
return bech32.Encode("npub", bits5)
|
||||
}
|
||||
|
||||
func EncodeNote(eventIdHex string) (string, error) {
|
||||
b, err := hex.DecodeString(eventIdHex)
|
||||
func EncodeNote(eventIDHex string) (string, error) {
|
||||
b, err := hex.DecodeString(eventIDHex)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
func EncodeEvent(eventIdHex string, relays []string, author string) (string, error) {
|
||||
func EncodeEvent(eventIDHex string, relays []string, author string) (string, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
id, err := hex.DecodeString(eventIdHex)
|
||||
id, err := hex.DecodeString(eventIDHex)
|
||||
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)
|
||||
|
||||
|
@ -55,15 +55,14 @@ func CheckDelegation(ev *nostr.Event) (ok bool, err error) {
|
||||
d := new(DelegationToken)
|
||||
if ok, err := d.Parse(ev); ok == true && (err == nil || err == NoDelegationTag) {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Import verifies that t is NIP-26 delegation token for the given delegatee.
|
||||
// The returned DelegationToken object can be used in DelegatedSign.
|
||||
// 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)
|
||||
if len(t) == 4 && t[0] == "delegation" {
|
||||
copy(d.tag[:], t)
|
||||
@ -81,16 +80,16 @@ func Import(t nostr.Tag, delegatee_pk string) (d *DelegationToken, e error) {
|
||||
}
|
||||
|
||||
// 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[:])
|
||||
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[:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error: %s", err.Error())
|
||||
return nil, fmt.Errorf("error: %s", err.Error())
|
||||
}
|
||||
if !sig.Verify(h[:], pubkey) {
|
||||
return nil, VerificationFailed
|
||||
@ -131,15 +130,15 @@ jump1:
|
||||
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:
|
||||
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) {
|
||||
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
|
||||
@ -147,12 +146,12 @@ jump2:
|
||||
|
||||
sig, err := schnorr.ParseSignature(d.token[:])
|
||||
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[:])
|
||||
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
|
||||
@ -160,14 +159,16 @@ jump2:
|
||||
|
||||
// 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.
|
||||
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 {
|
||||
if t[0] == "delegation" {
|
||||
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) {
|
||||
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 {
|
||||
for _, k := range d.kinds {
|
||||
@ -175,34 +176,36 @@ func DelegatedSign(ev *nostr.Event, d *DelegationToken, delegatee_sk string) err
|
||||
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:
|
||||
if pk, e := nostr.GetPublicKey(delegatee_sk); e != nil {
|
||||
return fmt.Errorf("invalid delegatee secret key.")
|
||||
if pk, e := nostr.GetPublicKey(delegateeSk); e != nil {
|
||||
return fmt.Errorf("invalid delegatee secret key")
|
||||
} else {
|
||||
ev.PubKey = pk
|
||||
}
|
||||
ev.Tags = append(ev.Tags, d.Tag())
|
||||
return ev.Sign(delegatee_sk)
|
||||
return ev.Sign(delegateeSk)
|
||||
}
|
||||
|
||||
// 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)
|
||||
s, e := hex.DecodeString(delegator_sk)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("invalid delegator secret key")
|
||||
}
|
||||
|
||||
tee_pk, e := hex.DecodeString(delegatee_pk)
|
||||
if len(tee_pk) != 32 || e != nil {
|
||||
teePk, e := hex.DecodeString(delegatee_pk)
|
||||
if len(teePk) != 32 || e != nil {
|
||||
return nil, fmt.Errorf("invalid delegatee pubkey")
|
||||
}
|
||||
|
||||
// set delegator
|
||||
sk, tor_pk := btcec.PrivKeyFromBytes(s)
|
||||
copy(d.delegator[:], schnorr.SerializePubKey(tor_pk))
|
||||
sk, torPk := btcec.PrivKeyFromBytes(s)
|
||||
copy(d.delegator[:], schnorr.SerializePubKey(torPk))
|
||||
|
||||
d.kinds = kinds
|
||||
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[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 {
|
||||
panic(err)
|
||||
|
@ -10,9 +10,9 @@ import (
|
||||
func TestDelegateSign(t *testing.T) {
|
||||
since := time.Unix(1600000000, 0)
|
||||
until := time.Unix(1600000100, 0)
|
||||
delegator_secret_key, delegatee_secret_key := "3f0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459da", "e9142f724955c5854de36324dab0434f97b15ec6b33464d56ebe491e3f559d1b"
|
||||
delegatee_pubkey, _ := nostr.GetPublicKey(delegatee_secret_key)
|
||||
d1, err := CreateToken(delegator_secret_key, delegatee_pubkey, []int{1, 2, 3}, &since, &until)
|
||||
delegatorSecretKey, delegatee_secret_key := "3f0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459da", "e9142f724955c5854de36324dab0434f97b15ec6b33464d56ebe491e3f559d1b"
|
||||
delegateePubkey, _ := nostr.GetPublicKey(delegatee_secret_key)
|
||||
d1, err := CreateToken(delegatorSecretKey, delegateePubkey, []int{1, 2, 3}, &since, &until)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@ -23,7 +23,7 @@ func TestDelegateSign(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
d2, err := Import(d1.Tag(), delegatee_pubkey)
|
||||
d2, err := Import(d1.Tag(), delegateePubkey)
|
||||
if err != nil {
|
||||
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"}
|
||||
d3, err := Import(tag, delegatee_pubkey)
|
||||
d3, err := Import(tag, delegateePubkey)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ func CreateUnsignedAuthEvent(challenge, pubkey, relayURL string) nostr.Event {
|
||||
}
|
||||
}
|
||||
|
||||
// helper function for ValidateAuthEvent
|
||||
func parseUrl(input string) (*url.URL, error) {
|
||||
// helper function for ValidateAuthEvent.
|
||||
func parseURL(input string) (*url.URL, error) {
|
||||
return url.Parse(
|
||||
strings.ToLower(
|
||||
strings.TrimSuffix(input, "/"),
|
||||
@ -43,12 +43,12 @@ func ValidateAuthEvent(event *nostr.Event, challenge string, relayURL string) (p
|
||||
return "", false
|
||||
}
|
||||
|
||||
expected, err := parseUrl(relayURL)
|
||||
expected, err := parseURL(relayURL)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
found, err := parseUrl(event.Tags.GetFirst([]string{"relay", ""}).Value())
|
||||
found, err := parseURL(event.Tags.GetFirst([]string{"relay", ""}).Value())
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
@ -43,13 +43,13 @@ const (
|
||||
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) {
|
||||
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) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@ -59,7 +59,7 @@ func Unmarshal(data string, evt *nostr.Event) (err error) {
|
||||
|
||||
// check if it's nson
|
||||
if data[NSON_MARKER_START:NSON_MARKER_END] != ",\"nson\":" {
|
||||
return NotNSON
|
||||
return ErrNotNSON
|
||||
}
|
||||
|
||||
// nson values
|
||||
@ -168,7 +168,8 @@ func Marshal(evt *nostr.Event) (string, error) {
|
||||
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":"`)
|
||||
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 {
|
||||
|
@ -72,7 +72,6 @@ func checkParsedCorrectly(t *testing.T, evt *nostr.Event, jevt string) (isBad bo
|
||||
err := json.Unmarshal([]byte(jevt), &canonical)
|
||||
if err != nil {
|
||||
t.Fatalf("error unmarshaling normal json: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if evt.ID != canonical.ID {
|
||||
|
5
relay.go
5
relay.go
@ -23,7 +23,7 @@ const (
|
||||
PublishStatusSucceeded Status = 1
|
||||
)
|
||||
|
||||
var subscriptionIdCounter atomic.Int32
|
||||
var subscriptionIDCounter atomic.Int32
|
||||
|
||||
func (s Status) String() string {
|
||||
switch s {
|
||||
@ -204,7 +204,6 @@ func (r *Relay) Connect(ctx context.Context) error {
|
||||
go sub.Unsub()
|
||||
return true
|
||||
})
|
||||
return
|
||||
}()
|
||||
|
||||
// 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()"))
|
||||
}
|
||||
|
||||
current := subscriptionIdCounter.Add(1)
|
||||
current := subscriptionIDCounter.Add(1)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
sub := &Subscription{
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"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 {
|
||||
// handle if it is a hex string
|
||||
if len(input) == 64 {
|
||||
@ -38,7 +38,7 @@ func InputToProfile(ctx context.Context, input string) *nostr.ProfilePointer {
|
||||
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 {
|
||||
// handle if it is a hex string
|
||||
if len(input) == 64 {
|
||||
|
@ -33,7 +33,7 @@ func ParseReferences(evt *nostr.Event) []*Reference {
|
||||
}
|
||||
|
||||
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]]
|
||||
|
||||
if prefix, data, err := nip19.Decode(nip19code); err == nil {
|
||||
|
@ -117,7 +117,7 @@ func (sub *Subscription) Close() {
|
||||
|
||||
// Sub sets sub.Filters and then calls sub.Fire(ctx).
|
||||
// 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.Fire()
|
||||
}
|
||||
|
8
tags.go
8
tags.go
@ -12,10 +12,15 @@ type Tag []string
|
||||
|
||||
// StartsWith checks if a tag contains a prefix.
|
||||
// for example,
|
||||
//
|
||||
// ["p", "abcdef...", "wss://relay.com"]
|
||||
//
|
||||
// would match against
|
||||
//
|
||||
// ["p", "abcdef..."]
|
||||
//
|
||||
// or even
|
||||
//
|
||||
// ["p", "abcdef...", "wss://"]
|
||||
func (tag Tag) StartsWith(prefix []string) bool {
|
||||
prefixLen := len(prefix)
|
||||
@ -109,9 +114,8 @@ func (tags Tags) AppendUnique(tag Tag) Tags {
|
||||
|
||||
if tags.GetFirst(tag[:n]) == nil {
|
||||
return append(tags, tag)
|
||||
} else {
|
||||
return tags
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
func (t *Tags) Scan(src any) error {
|
||||
|
Loading…
x
Reference in New Issue
Block a user