diff --git a/cmd/algia/config.go b/cmd/algia/config.go index cf4e3626..cd0a74a3 100644 --- a/cmd/algia/config.go +++ b/cmd/algia/config.go @@ -16,9 +16,7 @@ import ( "github.com/Hubmakerlabs/replicatr/pkg/context" "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/event" "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/filter" - "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/keys" "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/nip04" - "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/nip19" "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/relays" "github.com/fatih/color" ) @@ -30,6 +28,9 @@ type RelayPerms struct { Search bool `json:"search"` } +var rp = &RelayPerms{Read: true} +var wp = &RelayPerms{Write: true} + // Event is type Event struct { Event *event.T `json:"event"` @@ -44,54 +45,53 @@ type Profile struct { Lud16 string `json:"lud16"` DisplayName string `json:"display_name"` About string `json:"about"` - Name string `json:"name"` + Name string `json:"appName"` } -type Follows map[string]*Profile -type Relays map[string]*RelayPerms -type Emojis map[string]string +type ( + Follows map[string]*Profile + Relays map[string]*RelayPerms + Emojis map[string]string + Checklist map[string]struct{} + RelayIterator func(context.T, *relays.Relay) bool +) // C is the configuration for the client type C struct { - Relays `json:"relays"` - Follows `json:"follows"` - PrivateKey string `json:"privatekey"` - Updated time.Time `json:"updated"` - Emojis `json:"emojis"` - NwcURI string `json:"nwc-uri"` - NwcPub string `json:"nwc-pub"` - verbose bool - tempRelay bool - sk string + Relays `json:"relays"` + Follows `json:"follows"` + SecretKey string `json:"privatekey"` + Updated time.Time `json:"updated"` + Emojis `json:"emojis"` + NwcURI string `json:"nwc-uri"` + NwcPub string `json:"nwc-pub"` + verbose bool + tempRelay bool + sk string } -type RelayIterator func(context.T, *relays.Relay) bool +func (cfg *C) LastUpdated(t time.Duration) bool { + return cfg.Updated.Add(t).Before(time.Now()) +} -type Checklist map[string]struct{} - -var rp = &RelayPerms{Read: true} -var wp = &RelayPerms{Write: true} +func (cfg *C) Touch() { cfg.Updated = time.Now() } // GetFollows is -func (cfg *C) GetFollows(profile string) (profiles map[string]*Profile, e error) { +func (cfg *C) GetFollows(profile string) (profiles Follows, e error) { var mu sync.Mutex var pub string - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); log.Fail(e) { - return nil, e - } - if pub, e = keys.GetPublicKey(s.(string)); log.Fail(e) { - return nil, e + if pub, _, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { + return } // get followers - if (cfg.Updated.Add(3*time.Hour).Before(time.Now()) && !cfg.tempRelay) || + if (cfg.LastUpdated(3*time.Hour) && !cfg.tempRelay) || len(cfg.Follows) == 0 { mu.Lock() - cfg.Follows = Follows{} + cfg.Follows = make(Follows) mu.Unlock() - m := Checklist{} - cfg.Do(rp, cfg.aoeu(pub, m, &mu)) + m := make(Checklist) + cfg.Do(rp, cfg.GetRelaysAndTags(pub, m, &mu)) log.D.F("found %d followers\n", len(m)) if len(m) > 0 { var follows []string @@ -109,14 +109,14 @@ func (cfg *C) GetFollows(profile string) (profiles map[string]*Profile, e error) cfg.Do(rp, cfg.PopulateFollows(follows, i, end, &mu)) } } - cfg.Updated = time.Now() + cfg.Touch() if e = cfg.save(profile); log.Fail(e) { return nil, e } } return cfg.Follows, nil } -func (cfg *C) aoeu(pub string, m Checklist, mu *sync.Mutex) RelayIterator { +func (cfg *C) GetRelaysAndTags(pub string, m Checklist, mu *sync.Mutex) RelayIterator { return func(c context.T, rl *relays.Relay) bool { evs, e := rl.QuerySync(c, filter.T{ Kinds: []int{event.KindContactList}, @@ -175,7 +175,7 @@ func (cfg *C) PopulateFollows(f []string, i, end int, mu *sync.Mutex) RelayItera } // FindRelay is -func (cfg *C) FindRelay(c context.T, r RelayPerms) *relays.Relay { +func (cfg *C) FindRelay(c context.T, r *RelayPerms) *relays.Relay { for k, v := range cfg.Relays { if r.Write && !v.Write { continue @@ -186,9 +186,7 @@ func (cfg *C) FindRelay(c context.T, r RelayPerms) *relays.Relay { if !r.Write && !v.Read { continue } - if cfg.verbose { - fmt.Printf("trying relay: %s\n", k) - } + log.D.F("trying relay: %s", k) rl, e := relays.RelayConnect(c, k) if log.Fail(e) { continue @@ -256,13 +254,9 @@ func (cfg *C) save(profile string) (e error) { func (cfg *C) Decode(ev *event.T) (e error) { var sk string var pub string - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); log.Fail(e) { + if pub, _, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { return } - if pub, e = keys.GetPublicKey(s.(string)); log.Fail(e) { - return e - } tag := ev.Tags.GetFirst([]string{"p"}) if tag == nil { return errors.New("is not author") @@ -365,24 +359,24 @@ func (cfg *C) Events(f filter.T) []*event.T { return true }) - keys := []string{} + var kk []string m.Range(func(k, v any) bool { - keys = append(keys, k.(string)) + kk = append(kk, k.(string)) return true }) - sort.Slice(keys, func(i, j int) bool { - lhs, ok := m.Load(keys[i]) + sort.Slice(kk, func(i, j int) bool { + lhs, ok := m.Load(kk[i]) if !ok { return false } - rhs, ok := m.Load(keys[j]) + rhs, ok := m.Load(kk[j]) if !ok { return false } return lhs.(*event.T).CreatedAt.Time().Before(rhs.(*event.T).CreatedAt.Time()) }) var evs []*event.T - for _, key := range keys { + for _, key := range kk { vv, ok := m.Load(key) if !ok { continue @@ -394,30 +388,26 @@ func (cfg *C) Events(f filter.T) []*event.T { // ZapInfo is func (cfg *C) ZapInfo(pub string) (*Lnurlp, error) { - rl := cfg.FindRelay(context.Bg(), RelayPerms{Read: true}) + rl := cfg.FindRelay(context.Bg(), rp) if rl == nil { return nil, errors.New("cannot connect relays") } defer rl.Close() - // get set-metadata f := filter.T{ Kinds: []int{event.KindProfileMetadata}, Authors: []string{pub}, Limit: 1, } - evs := cfg.Events(f) if len(evs) == 0 { return nil, errors.New("cannot find user") } - var profile Profile e := json.Unmarshal([]byte(evs[0].Content), &profile) if log.Fail(e) { return nil, e } - tok := strings.SplitN(profile.Lud16, "@", 2) if log.Fail(e) { return nil, e @@ -425,16 +415,15 @@ func (cfg *C) ZapInfo(pub string) (*Lnurlp, error) { if len(tok) != 2 { return nil, errors.New("receipt address is not valid") } - - resp, e := http.Get("https://" + tok[1] + "/.well-known/lnurlp/" + tok[0]) + var resp *http.Response + resp, e = http.Get("https://" + tok[1] + "/.well-known/lnurlp/" + tok[0]) if log.Fail(e) { return nil, e } - defer resp.Body.Close() + defer log.Fail(resp.Body.Close()) var lp Lnurlp - e = json.NewDecoder(resp.Body).Decode(&lp) - if log.Fail(e) { + if e = json.NewDecoder(resp.Body).Decode(&lp); log.Fail(e) { return nil, e } return &lp, nil diff --git a/cmd/algia/keys.go b/cmd/algia/keys.go new file mode 100644 index 00000000..0e0ad222 --- /dev/null +++ b/cmd/algia/keys.go @@ -0,0 +1,18 @@ +package main + +import ( + "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/keys" + "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/nip19" +) + +func getPubFromSec(sk string) (pubHex string, secHex string, e error) { + var s any + if _, s, e = nip19.Decode(sk); log.Fail(e) { + return + } + secHex = s.(string) + if pubHex, e = keys.GetPublicKey(secHex); log.Fail(e) { + return + } + return +} diff --git a/cmd/algia/main.go b/cmd/algia/main.go index 860d3844..0414b8b5 100644 --- a/cmd/algia/main.go +++ b/cmd/algia/main.go @@ -15,18 +15,17 @@ import ( var log = log2.GetStd() -const name = "algia" +const appName = "algia" const version = "0.0.54" var revision = "HEAD" -func configDir() (string, error) { +func configDir() (dir string, e error) { switch runtime.GOOS { case "darwin": - dir, e := os.UserHomeDir() - if log.Fail(e) { - return "", e + if dir, e = os.UserHomeDir(); log.Fail(e) { + return } return filepath.Join(dir, ".config"), nil default: @@ -34,53 +33,55 @@ func configDir() (string, error) { } } -func loadConfig(profile string) (*C, error) { - dir, e := configDir() - if log.Fail(e) { +func loadConfig(profile string) (cfg *C, e error) { + var dir string + if dir, e = configDir(); log.Fail(e) { return nil, e } - dir = filepath.Join(dir, "algia") - + dir = filepath.Join(dir, appName) var fp string - if profile == "" { + switch profile { + case "": fp = filepath.Join(dir, "config.json") - } else if profile == "?" { - names, e := filepath.Glob(filepath.Join(dir, "config-*.json")) - if log.Fail(e) { - return nil, e + case "?": + var nn []string + p := filepath.Join(dir, "config-*.json") + if nn, e = filepath.Glob(p); log.Fail(e) { + return } - for _, n := range names { + for _, n := range nn { n = filepath.Base(n) n = strings.TrimLeft(n[6:len(n)-5], "-") fmt.Println(n) } os.Exit(0) - } else { + default: fp = filepath.Join(dir, "config-"+profile+".json") } - log.Fail(os.MkdirAll(filepath.Dir(fp), 0700)) - - b, e := os.ReadFile(fp) - if log.Fail(e) { - return nil, e + if e = os.MkdirAll(filepath.Dir(fp), 0700); log.Fail(e) { + return } - var cfg C - e = json.Unmarshal(b, &cfg) - if log.Fail(e) { - return nil, e + var b []byte + if b, e = os.ReadFile(fp); log.Fail(e) { + return + } + cfg = new(C) + if e = json.Unmarshal(b, cfg); log.Fail(e) { + return } if len(cfg.Relays) == 0 { - cfg.Relays = map[string]*RelayPerms{} - cfg.Relays["wss://relay.nostr.band"] = &RelayPerms{ - Read: true, - Write: true, - Search: true, + cfg.Relays = Relays{ + "wss://relay.nostr.band": { + Read: true, + Write: true, + Search: true, + }, } } - return &cfg, nil + return } -func doVersion(cCtx *cli.Context) (e error) { +func doVersion(_ *cli.Context) (e error) { fmt.Println(version) return nil } @@ -90,7 +91,7 @@ func main() { Usage: "A cli application for nostr", Description: "A cli application for nostr", Flags: []cli.Flag{ - &cli.StringFlag{Name: "a", Usage: "profile name"}, + &cli.StringFlag{Name: "a", Usage: "profile appName"}, &cli.StringFlag{Name: "relays", Usage: "relays"}, &cli.BoolFlag{Name: "V", Usage: "verbose"}, }, @@ -129,7 +130,7 @@ func main() { &cli.StringFlag{Name: "geohash"}, }, Usage: "post new note", - UsageText: "algia post [note text]", + UsageText: appName + " post [note text]", HelpName: "post", ArgsUsage: "[note text]", Action: doPost, @@ -146,7 +147,7 @@ func main() { &cli.StringFlag{Name: "geohash"}, }, Usage: "reply to the note", - UsageText: "algia reply --id [id] [note text]", + UsageText: appName + " reply --id [id] [note text]", HelpName: "reply", ArgsUsage: "[note text]", Action: doReply, @@ -158,7 +159,7 @@ func main() { &cli.StringFlag{Name: "id", Required: true}, }, Usage: "repost the note", - UsageText: "algia repost --id [id]", + UsageText: appName + " repost --id [id]", HelpName: "repost", Action: doRepost, }, @@ -169,7 +170,7 @@ func main() { &cli.StringFlag{Name: "id", Required: true}, }, Usage: "unrepost the note", - UsageText: "algia unrepost --id [id]", + UsageText: appName + " unrepost --id [id]", HelpName: "unrepost", Action: doUnrepost, }, @@ -182,7 +183,7 @@ func main() { &cli.StringFlag{Name: "emoji"}, }, Usage: "like the note", - UsageText: "algia like --id [id]", + UsageText: appName + " like --id [id]", HelpName: "like", Action: doLike, }, @@ -193,7 +194,7 @@ func main() { &cli.StringFlag{Name: "id", Required: true}, }, Usage: "unlike the note", - UsageText: "algia unlike --id [id]", + UsageText: appName + " unlike --id [id]", HelpName: "unlike", Action: doUnlike, }, @@ -204,7 +205,7 @@ func main() { &cli.StringFlag{Name: "id", Required: true}, }, Usage: "delete the note", - UsageText: "algia delete --id [id]", + UsageText: appName + " delete --id [id]", HelpName: "delete", Action: doDelete, }, @@ -217,7 +218,7 @@ func main() { &cli.BoolFlag{Name: "extra", Usage: "extra JSON"}, }, Usage: "search notes", - UsageText: "algia search [words]", + UsageText: appName + " search [words]", HelpName: "search", Action: doSearch, }, @@ -247,7 +248,7 @@ func main() { &cli.StringFlag{Name: "sensitive"}, }, Usage: "post new note", - UsageText: "algia post [note text]", + UsageText: appName + " post [note text]", HelpName: "post", ArgsUsage: "[note text]", Action: doDMPost, @@ -259,21 +260,21 @@ func main() { &cli.BoolFlag{Name: "json", Usage: "output JSON"}, }, Usage: "show profile", - UsageText: "algia profile", + UsageText: appName + " profile", HelpName: "profile", Action: doProfile, }, { Name: "powa", Usage: "post ぽわ〜", - UsageText: "algia powa", + UsageText: appName + " powa", HelpName: "powa", Action: doPowa, }, { Name: "puru", Usage: "post ぷる", - UsageText: "algia puru", + UsageText: appName + " puru", HelpName: "puru", Action: doPuru, }, @@ -284,14 +285,14 @@ func main() { &cli.StringFlag{Name: "comment", Usage: "comment for zap", Value: ""}, }, Usage: "zap [note|npub|nevent]", - UsageText: "algia zap [note|npub|nevent]", + UsageText: appName + " zap [note|npub|nevent]", HelpName: "zap", Action: doZap, }, { Name: "version", Usage: "show version", - UsageText: "algia version", + UsageText: appName + " version", HelpName: "version", Action: doVersion, }, @@ -301,8 +302,8 @@ func main() { return nil } profile := cCtx.String("a") - cfg, e := loadConfig(profile) - if log.Fail(e) { + var cfg *C + if cfg, e = loadConfig(profile); log.Fail(e) { return e } cCtx.App.Metadata = map[string]any{ @@ -314,7 +315,7 @@ func main() { } relays := cCtx.String("relays") if strings.TrimSpace(relays) != "" { - cfg.Relays = make(map[string]*RelayPerms) + cfg.Relays = make(Relays) for _, rl := range strings.Split(relays, ",") { cfg.Relays[rl] = &RelayPerms{ Read: true, diff --git a/cmd/algia/profile.go b/cmd/algia/profile.go index ce3c5cd4..5068ee46 100644 --- a/cmd/algia/profile.go +++ b/cmd/algia/profile.go @@ -9,32 +9,25 @@ import ( "github.com/Hubmakerlabs/replicatr/pkg/context" "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/event" "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/filter" - "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/keys" "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/nip19" + "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/relays" "github.com/Hubmakerlabs/replicatr/pkg/nostr-sdk" "github.com/urfave/cli/v2" ) func doProfile(cCtx *cli.Context) (e error) { - user := cCtx.String("u") - j := cCtx.Bool("json") - + user, j := cCtx.String("u"), cCtx.Bool("json") cfg := cCtx.App.Metadata["config"].(*C) - rl := cfg.FindRelay(context.Bg(), RelayPerms{Read: true}) - if rl == nil { + var rl *relays.Relay + if rl = cfg.FindRelay(context.Bg(), rp); rl == nil { return errors.New("cannot connect relays") } defer log.E.Chk(rl.Close()) - var pub string if user == "" { - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); log.Fail(e) { + if pub, _, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { return } - if pub, e = keys.GetPublicKey(s.(string)); log.Fail(e) { - return e - } } else { if pp := sdk.InputToProfile(context.TODO(), user); pp != nil { pub = pp.PublicKey diff --git a/cmd/algia/timeline.go b/cmd/algia/timeline.go index 7c07eec9..e4b81572 100644 --- a/cmd/algia/timeline.go +++ b/cmd/algia/timeline.go @@ -16,7 +16,6 @@ import ( "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/event" "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/filter" "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/filters" - "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/keys" "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/relays" "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/tags" "github.com/Hubmakerlabs/replicatr/pkg/go-nostr/timestamp" @@ -30,31 +29,22 @@ import ( func doDMList(cCtx *cli.Context) (e error) { j := cCtx.Bool("json") - cfg := cCtx.App.Metadata["config"].(*C) - // get followers var followsMap Follows followsMap, e = cfg.GetFollows(cCtx.String("a")) if log.Fail(e) { return e } - - var npub string - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); log.Fail(e) { + var pub string + if pub, _, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { return } - if npub, e = keys.GetPublicKey(s.(string)); log.Fail(e) { - return e - } - // get timeline f := filter.T{ Kinds: []int{event.KindEncryptedDirectMessage}, - Authors: []string{npub}, + Authors: []string{pub}, } - evs := cfg.Events(f) type entry struct { name string @@ -83,11 +73,10 @@ func doDMList(cCtx *cli.Context) (e error) { } if j { for _, user := range users { - json.NewEncoder(os.Stdout).Encode(user) + log.Fail(json.NewEncoder(os.Stdout).Encode(user)) } return nil } - for _, user := range users { color.Set(color.FgHiRed) fmt.Print(user.name) @@ -104,18 +93,11 @@ func doDMTimeline(cCtx *cli.Context) (e error) { u := cCtx.String("u") j := cCtx.Bool("json") extra := cCtx.Bool("extra") - cfg := cCtx.App.Metadata["config"].(*C) - var npub string - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); log.Fail(e) { + if npub, _, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { return } - if npub, e = keys.GetPublicKey(s.(string)); log.Fail(e) { - return e - } - if u == "me" { u = npub } @@ -126,11 +108,10 @@ func doDMTimeline(cCtx *cli.Context) (e error) { return fmt.Errorf("failed to parse pubkey from '%s'", u) } // get followers - followsMap, e := cfg.GetFollows(cCtx.String("a")) - if log.Fail(e) { + var followsMap Follows + if followsMap, e = cfg.GetFollows(cCtx.String("a")); log.Fail(e) { return e } - // get timeline f := filter.T{ Kinds: []int{event.KindEncryptedDirectMessage}, @@ -138,7 +119,6 @@ func doDMTimeline(cCtx *cli.Context) (e error) { Tags: filter.TagMap{"p": []string{npub, pub}}, Limit: 9999, } - evs := cfg.Events(f) cfg.PrintEvents(evs, followsMap, j, extra) return nil @@ -151,22 +131,15 @@ func doDMPost(cCtx *cli.Context) (e error) { return cli.ShowSubcommandHelp(cCtx) } sensitive := cCtx.String("sensitive") - cfg := cCtx.App.Metadata["config"].(*C) - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); log.Fail(e) { - return - } - sk := s.(string) - ev := &event.T{} - var npub string - if npub, e = keys.GetPublicKey(sk); log.Fail(e) { + var pubHex, secHex string + if pubHex, secHex, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { return } - if _, e := nip19.EncodePublicKey(npub); log.Fail(e) { + if _, e = nip19.EncodePublicKey(pubHex); log.Fail(e) { return e } - ev.PubKey = npub + ev := &event.T{PubKey: pubHex} if stdin { var b []byte b, e = io.ReadAll(os.Stdin) @@ -180,11 +153,9 @@ func doDMPost(cCtx *cli.Context) (e error) { if strings.TrimSpace(ev.Content) == "" { return errors.New("content is empty") } - if sensitive != "" { ev.Tags = ev.Tags.AppendUnique(tags.Tag{"content-warning", sensitive}) } - if u == "me" { u = ev.PubKey } @@ -194,29 +165,22 @@ func doDMPost(cCtx *cli.Context) (e error) { } else { return fmt.Errorf("failed to parse pubkey from '%s'", u) } - ev.Tags = ev.Tags.AppendUnique(tags.Tag{"p", pub}) ev.CreatedAt = timestamp.Now() ev.Kind = event.KindEncryptedDirectMessage - - ss, e := nip04.ComputeSharedSecret(ev.PubKey, sk) - if log.Fail(e) { - return e + var secret []byte + if secret, e = nip04.ComputeSharedSecret(ev.PubKey, secHex); log.Fail(e) { + return } - ev.Content, e = nip04.Encrypt(ev.Content, ss) - if log.Fail(e) { - return e + if ev.Content, e = nip04.Encrypt(ev.Content, secret); log.Fail(e) { + return } - if e := ev.Sign(sk); log.Fail(e) { - return e + if e = ev.Sign(secHex); log.Fail(e) { + return } - var success atomic.Int64 cfg.Do(wp, func(c context.T, rl *relays.Relay) bool { - e := rl.Publish(c, ev) - if log.Fail(e) { - fmt.Fprintln(os.Stderr, rl.URL, e) - } else { + if e := rl.Publish(c, ev); !log.Fail(e) { success.Add(1) } return true @@ -226,6 +190,7 @@ func doDMPost(cCtx *cli.Context) (e error) { } return nil } + func (cfg *C) publish(ev *event.T, success *atomic.Int64) RelayIterator { return func(c context.T, rl *relays.Relay) bool { e := rl.Publish(c, ev) @@ -237,30 +202,19 @@ func (cfg *C) publish(ev *event.T, success *atomic.Int64) RelayIterator { return true } } + func doPost(cCtx *cli.Context) (e error) { stdin := cCtx.Bool("stdin") if !stdin && cCtx.Args().Len() == 0 { return cli.ShowSubcommandHelp(cCtx) } - sensitive := cCtx.String("sensitive") - geohash := cCtx.String("geohash") - + sensitive, geohash := cCtx.String("sensitive"), cCtx.String("geohash") cfg := cCtx.App.Metadata["config"].(*C) - - var sk string - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); log.Fail(e) { + var pub, sk string + if pub, sk, e = getPubFromSec(sk); log.Fail(e) { return } - sk = s.(string) ev := &event.T{} - var pub string - if pub, e = keys.GetPublicKey(sk); log.Fail(e) { - return - } - if _, e = nip19.EncodePublicKey(pub); log.Fail(e) { - return e - } ev.PubKey = pub if stdin { var b []byte @@ -274,13 +228,10 @@ func doPost(cCtx *cli.Context) (e error) { if strings.TrimSpace(ev.Content) == "" { return errors.New("content is empty") } - ev.Tags = tags.Tags{} - for _, link := range extractLinks(ev.Content) { ev.Tags = ev.Tags.AppendUnique(tags.Tag{"r", link.text}) } - for _, u := range cCtx.StringSlice("emoji") { tok := strings.SplitN(u, "=", 2) if len(tok) != 2 { @@ -294,7 +245,6 @@ func doPost(cCtx *cli.Context) (e error) { ev.Tags = ev.Tags.AppendUnique(tags.Tag{"emoji", emoji, icon}) } } - for i, u := range cCtx.StringSlice("u") { ev.Content = fmt.Sprintf("#[%d] ", i) + ev.Content if pp := sdk.InputToProfile(context.TODO(), u); pp != nil { @@ -304,15 +254,12 @@ func doPost(cCtx *cli.Context) (e error) { } ev.Tags = ev.Tags.AppendUnique(tags.Tag{"p", u}) } - if sensitive != "" { ev.Tags = ev.Tags.AppendUnique(tags.Tag{"content-warning", sensitive}) } - if geohash != "" { ev.Tags = ev.Tags.AppendUnique(tags.Tag{"g", geohash}) } - hashtag := tags.Tag{"h"} for _, m := range regexp.MustCompile(`#[a-zA-Z0-9]+`).FindAllStringSubmatchIndex(ev.Content, -1) { hashtag = append(hashtag, ev.Content[m[0]+1:m[1]]) @@ -320,18 +267,16 @@ func doPost(cCtx *cli.Context) (e error) { if len(hashtag) > 1 { ev.Tags = ev.Tags.AppendUnique(hashtag) } - ev.CreatedAt = timestamp.Now() ev.Kind = event.KindTextNote - if e := ev.Sign(sk); log.Fail(e) { + if e = ev.Sign(sk); log.Fail(e) { return e } - var success atomic.Int64 cfg.Do(wp, func(c context.T, rl *relays.Relay) bool { e := rl.Publish(c, ev) if log.Fail(e) { - fmt.Fprintln(os.Stderr, rl.URL, e) + log.D.Ln(rl.URL, e) } else { success.Add(1) } @@ -344,44 +289,30 @@ func doPost(cCtx *cli.Context) (e error) { } func doReply(cCtx *cli.Context) (e error) { - stdin := cCtx.Bool("stdin") - id := cCtx.String("id") - quote := cCtx.Bool("quote") + stdin, id, quote := cCtx.Bool("stdin"), cCtx.String("id"), + cCtx.Bool("quote") if !stdin && cCtx.Args().Len() == 0 { return cli.ShowSubcommandHelp(cCtx) } - sensitive := cCtx.String("sensitive") - geohash := cCtx.String("geohash") - + sensitive, geohash := cCtx.String("sensitive"), cCtx.String("geohash") cfg := cCtx.App.Metadata["config"].(*C) - - var sk string - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); log.Fail(e) { + var sk, pub string + if pub, sk, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { return } - sk = s.(string) ev := &event.T{} - var pub string - if pub, e = keys.GetPublicKey(sk); log.Fail(e) { - return - } - if _, e = nip19.EncodePublicKey(pub); log.Fail(e) { - return - } ev.PubKey = pub if evp := sdk.InputToEventPointer(id); evp != nil { id = evp.ID } else { return fmt.Errorf("failed to parse event from '%s'", id) } - ev.CreatedAt = timestamp.Now() ev.Kind = event.KindTextNote if stdin { - b, e := io.ReadAll(os.Stdin) - if log.Fail(e) { - return e + var b []byte + if b, e = io.ReadAll(os.Stdin); log.Fail(e) { + return } ev.Content = string(b) } else { @@ -390,13 +321,10 @@ func doReply(cCtx *cli.Context) (e error) { if strings.TrimSpace(ev.Content) == "" { return errors.New("content is empty") } - ev.Tags = tags.Tags{} - for _, link := range extractLinks(ev.Content) { ev.Tags = ev.Tags.AppendUnique(tags.Tag{"r", link.text}) } - for _, u := range cCtx.StringSlice("emoji") { tok := strings.SplitN(u, "=", 2) if len(tok) != 2 { @@ -410,15 +338,12 @@ func doReply(cCtx *cli.Context) (e error) { ev.Tags = ev.Tags.AppendUnique(tags.Tag{"emoji", emoji, icon}) } } - if sensitive != "" { ev.Tags = ev.Tags.AppendUnique(tags.Tag{"content-warning", sensitive}) } - if geohash != "" { ev.Tags = ev.Tags.AppendUnique(tags.Tag{"g", geohash}) } - hashtag := tags.Tag{"h"} for _, m := range regexp.MustCompile(`#[a-zA-Z0-9]+`).FindAllStringSubmatchIndex(ev.Content, -1) { hashtag = append(hashtag, ev.Content[m[0]+1:m[1]]) @@ -426,7 +351,6 @@ func doReply(cCtx *cli.Context) (e error) { if len(hashtag) > 1 { ev.Tags = ev.Tags.AppendUnique(hashtag) } - var success atomic.Int64 cfg.Do(wp, func(c context.T, rl *relays.Relay) bool { if !quote { @@ -437,9 +361,8 @@ func doReply(cCtx *cli.Context) (e error) { if e := ev.Sign(sk); log.Fail(e) { return true } - e := rl.Publish(c, ev) - if log.Fail(e) { - fmt.Fprintln(os.Stderr, rl.URL, e) + if e = rl.Publish(c, ev); log.Fail(e) { + log.D.Ln(rl.URL, e) } else { success.Add(1) } @@ -453,21 +376,10 @@ func doReply(cCtx *cli.Context) (e error) { func doRepost(cCtx *cli.Context) (e error) { id := cCtx.String("id") - cfg := cCtx.App.Metadata["config"].(*C) - ev := &event.T{} - var sk string - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); log.Fail(e) { - return - } - sk = s.(string) - var pub string - if pub, e = keys.GetPublicKey(sk); log.Fail(e) { - return - } - if _, e = nip19.EncodePublicKey(pub); log.Fail(e) { + var sk, pub string + if pub, sk, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { return } ev.PubKey = pub @@ -481,14 +393,11 @@ func doRepost(cCtx *cli.Context) (e error) { Kinds: []int{event.KindTextNote}, IDs: []string{id}, } - ev.CreatedAt = timestamp.Now() ev.Kind = event.KindRepost ev.Content = "" - var first atomic.Bool first.Store(true) - var success atomic.Int64 cfg.Do(wp, func(c context.T, rl *relays.Relay) bool { if first.Load() { @@ -500,13 +409,12 @@ func doRepost(cCtx *cli.Context) (e error) { ev.Tags = ev.Tags.AppendUnique(tags.Tag{"p", tmp.ID}) } first.Store(false) - if e := ev.Sign(sk); log.Fail(e) { + if e = ev.Sign(sk); log.Fail(e) { return true } } - e := rl.Publish(c, ev) - if log.Fail(e) { - fmt.Fprintln(os.Stderr, rl.URL, e) + if e = rl.Publish(c, ev); log.Fail(e) { + log.D.Ln(rl.URL, e) } else { success.Add(1) } @@ -525,17 +433,9 @@ func doUnrepost(cCtx *cli.Context) (e error) { } else { return fmt.Errorf("failed to parse event from '%s'", id) } - cfg := cCtx.App.Metadata["config"].(*C) - - var sk string - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); log.Fail(e) { - return - } - sk = s.(string) - var pub string - if pub, e = keys.GetPublicKey(sk); log.Fail(e) { + var sk, pub string + if pub, sk, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { return } f := filter.T{ @@ -557,18 +457,16 @@ func doUnrepost(cCtx *cli.Context) (e error) { mu.Unlock() return true }) - ev := &event.T{} ev.Tags = ev.Tags.AppendUnique(tags.Tag{"e", repostID}) ev.CreatedAt = timestamp.Now() ev.Kind = event.KindDeletion - if e := ev.Sign(sk); log.Fail(e) { + if e = ev.Sign(sk); log.Fail(e) { return e } - var success atomic.Int64 cfg.Do(wp, func(c context.T, rl *relays.Relay) bool { - e = rl.Publish(c, ev) + e := rl.Publish(c, ev) if log.Fail(e) { log.D.Ln(rl.URL, e) } else { @@ -584,26 +482,13 @@ func doUnrepost(cCtx *cli.Context) (e error) { func doLike(cCtx *cli.Context) (e error) { id := cCtx.String("id") - cfg := cCtx.App.Metadata["config"].(*C) - ev := &event.T{} - var sk string - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); log.Fail(e) { + var sk, pub string + if pub, sk, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { return } - sk = s.(string) - var pub string - if pub, e = keys.GetPublicKey(sk); e == nil { - if _, e := nip19.EncodePublicKey(pub); log.Fail(e) { - return e - } - ev.PubKey = pub - } else { - return e - } - + ev.PubKey = pub if evp := sdk.InputToEventPointer(id); evp != nil { id = evp.ID } else { @@ -614,7 +499,6 @@ func doLike(cCtx *cli.Context) (e error) { Kinds: []int{event.KindTextNote}, IDs: []string{id}, } - ev.CreatedAt = timestamp.Now() ev.Kind = event.KindReaction ev.Content = cCtx.String("content") @@ -629,10 +513,8 @@ func doLike(cCtx *cli.Context) (e error) { if ev.Content == "" { ev.Content = "+" } - var first atomic.Bool first.Store(true) - var success atomic.Int64 cfg.Do(wp, func(c context.T, rl *relays.Relay) bool { if first.Load() { @@ -644,14 +526,13 @@ func doLike(cCtx *cli.Context) (e error) { ev.Tags = ev.Tags.AppendUnique(tags.Tag{"p", tmp.ID}) } first.Store(false) - if e := ev.Sign(sk); log.Fail(e) { + if e = ev.Sign(sk); log.Fail(e) { return true } return true } - e := rl.Publish(c, ev) - if log.Fail(e) { - fmt.Fprintln(os.Stderr, rl.URL, e) + if e := rl.Publish(c, ev); log.Fail(e) { + log.D.Ln(rl.URL, e) } else { success.Add(1) } @@ -670,19 +551,10 @@ func doUnlike(cCtx *cli.Context) (e error) { } else { return fmt.Errorf("failed to parse event from '%s'", id) } - cfg := cCtx.App.Metadata["config"].(*C) - - var sk string - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); !log.Fail(e) { - sk = s.(string) - } else { - return e - } - pub, e := keys.GetPublicKey(sk) - if log.Fail(e) { - return e + var sk, pub string + if pub, sk, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { + return } f := filter.T{ Kinds: []int{event.KindReaction}, @@ -703,7 +575,6 @@ func doUnlike(cCtx *cli.Context) (e error) { mu.Unlock() return true }) - ev := &event.T{} ev.Tags = ev.Tags.AppendUnique(tags.Tag{"e", likeID}) ev.CreatedAt = timestamp.Now() @@ -711,13 +582,10 @@ func doUnlike(cCtx *cli.Context) (e error) { if e := ev.Sign(sk); log.Fail(e) { return e } - var success atomic.Int64 cfg.Do(wp, func(c context.T, rl *relays.Relay) bool { e := rl.Publish(c, ev) - if log.Fail(e) { - fmt.Fprintln(os.Stderr, rl.URL, e) - } else { + if !log.Fail(e) { success.Add(1) } return true @@ -730,26 +598,13 @@ func doUnlike(cCtx *cli.Context) (e error) { func doDelete(cCtx *cli.Context) (e error) { id := cCtx.String("id") - cfg := cCtx.App.Metadata["config"].(*C) - ev := &event.T{} - var sk string - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); !log.Fail(e) { - sk = s.(string) - } else { - return e - } - if pub, e := keys.GetPublicKey(sk); e == nil { - if _, e := nip19.EncodePublicKey(pub); log.Fail(e) { - return e - } - ev.PubKey = pub - } else { - return e + var pub, sk string + if pub, sk, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { + return } - + ev.PubKey = pub if evp := sdk.InputToEventPointer(id); evp != nil { id = evp.ID } else { @@ -758,16 +613,13 @@ func doDelete(cCtx *cli.Context) (e error) { ev.Tags = ev.Tags.AppendUnique(tags.Tag{"e", id}) ev.CreatedAt = timestamp.Now() ev.Kind = event.KindDeletion - if e := ev.Sign(sk); log.Fail(e) { + if e = ev.Sign(sk); log.Fail(e) { return e } - var success atomic.Int64 cfg.Do(wp, func(c context.T, rl *relays.Relay) bool { e := rl.Publish(c, ev) - if log.Fail(e) { - fmt.Fprintln(os.Stderr, rl.URL, e) - } else { + if !log.Fail(e) { success.Add(1) } return true @@ -782,9 +634,7 @@ func doSearch(cCtx *cli.Context) (e error) { n := cCtx.Int("n") j := cCtx.Bool("json") extra := cCtx.Bool("extra") - cfg := cCtx.App.Metadata["config"].(*C) - // get followers var followsMap Follows if j && !extra { @@ -795,14 +645,12 @@ func doSearch(cCtx *cli.Context) (e error) { return e } } - // get timeline f := filter.T{ Kinds: []int{event.KindTextNote}, Search: strings.Join(cCtx.Args().Slice(), " "), Limit: n, } - evs := cfg.Events(f) cfg.PrintEvents(evs, followsMap, j, extra) return nil @@ -814,7 +662,6 @@ func doStream(cCtx *cli.Context) (e error) { f := cCtx.Bool("follow") pattern := cCtx.String("pattern") reply := cCtx.String("reply") - var re *regexp.Regexp if pattern != "" { var e error @@ -823,33 +670,22 @@ func doStream(cCtx *cli.Context) (e error) { return e } } - cfg := cCtx.App.Metadata["config"].(*C) - - rl := cfg.FindRelay(context.Bg(), RelayPerms{Read: true}) + rl := cfg.FindRelay(context.Bg(), &RelayPerms{Read: true}) if rl == nil { return errors.New("cannot connect relays") } - defer rl.Close() - - var sk string - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); !log.Fail(e) { - sk = s.(string) - } else { - return e - } - pub, e := keys.GetPublicKey(sk) - if log.Fail(e) { - return e + defer log.Fail(rl.Close()) + var pub, sk string + if pub, sk, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { + return } - // get followers var follows []string if f { - followsMap, e := cfg.GetFollows(cCtx.String("a")) - if log.Fail(e) { - return e + followsMap := make(Follows) + if followsMap, e = cfg.GetFollows(cCtx.String("a")); log.Fail(e) { + return } for k := range followsMap { follows = append(follows, k) @@ -857,15 +693,14 @@ func doStream(cCtx *cli.Context) (e error) { } else { follows = authors } - since := timestamp.Now() ff := filter.T{ Kinds: kinds, Authors: follows, Since: &since, } - - sub, e := rl.Subscribe(context.Bg(), filters.T{ff}) + var sub *relays.Subscription + sub, e = rl.Subscribe(context.Bg(), filters.T{ff}) if log.Fail(e) { return e } @@ -874,7 +709,7 @@ func doStream(cCtx *cli.Context) (e error) { if re != nil && !re.MatchString(ev.Content) { continue } - json.NewEncoder(os.Stdout).Encode(ev) + log.Fail(json.NewEncoder(os.Stdout).Encode(ev)) if reply != "" { evr := &event.T{} evr.PubKey = pub @@ -891,7 +726,7 @@ func doStream(cCtx *cli.Context) (e error) { }) } } else { - json.NewEncoder(os.Stdout).Encode(ev) + log.Fail(json.NewEncoder(os.Stdout).Encode(ev)) } } return nil @@ -901,9 +736,7 @@ func doTimeline(cCtx *cli.Context) (e error) { n := cCtx.Int("n") j := cCtx.Bool("json") extra := cCtx.Bool("extra") - cfg := cCtx.App.Metadata["config"].(*C) - // get followers followsMap, e := cfg.GetFollows(cCtx.String("a")) if log.Fail(e) { @@ -913,14 +746,12 @@ func doTimeline(cCtx *cli.Context) (e error) { for k := range followsMap { follows = append(follows, k) } - // get timeline f := filter.T{ Kinds: []int{event.KindTextNote}, Authors: follows, Limit: n, } - evs := cfg.Events(f) cfg.PrintEvents(evs, followsMap, j, extra) return nil @@ -928,36 +759,24 @@ func doTimeline(cCtx *cli.Context) (e error) { func postMsg(cCtx *cli.Context, msg string) (e error) { cfg := cCtx.App.Metadata["config"].(*C) - - var sk string - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); !log.Fail(e) { - sk = s.(string) - } else { - return e + var pub, sk string + if pub, sk, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { + return } - ev := &event.T{} - var pub string - if pub, e = keys.GetPublicKey(sk); e == nil { - if _, e := nip19.EncodePublicKey(pub); log.Fail(e) { - return e - } - ev.PubKey = pub - } else { - return e + ev := &event.T{ + PubKey: pub, + CreatedAt: timestamp.Now(), + Kind: event.KindTextNote, + Tags: tags.Tags{}, + Content: msg, + Sig: "", } - - ev.Content = msg - ev.CreatedAt = timestamp.Now() - ev.Kind = event.KindTextNote - ev.Tags = tags.Tags{} - if e := ev.Sign(sk); log.Fail(e) { + if e = ev.Sign(sk); log.Fail(e) { return e } - var success atomic.Int64 cfg.Do(wp, func(c context.T, rl *relays.Relay) bool { - e = rl.Publish(c, ev) + e := rl.Publish(c, ev) if log.Fail(e) { log.D.Ln(rl.URL, e) } else { @@ -968,7 +787,7 @@ func postMsg(cCtx *cli.Context, msg string) (e error) { if success.Load() == 0 { return errors.New("cannot post") } - return nil + return } func doPowa(cCtx *cli.Context) (e error) { diff --git a/cmd/algia/zap.go b/cmd/algia/zap.go index 4d4ca52e..e98eeb62 100644 --- a/cmd/algia/zap.go +++ b/cmd/algia/zap.go @@ -154,35 +154,25 @@ func doZap(cCtx *cli.Context) (e error) { } cfg := cCtx.App.Metadata["config"].(*C) - - var sk string - var s any - if _, s, e = nip19.Decode(cfg.PrivateKey); log.Fail(e) { + var pub, sk string + if pub, sk, e = getPubFromSec(cfg.SecretKey); log.Fail(e) { return } - sk = s.(string) receipt := "" - zr := event.T{} - zr.Tags = tags.Tags{} - - if pub, e := keys.GetPublicKey(sk); e == nil { - if _, e := nip19.EncodePublicKey(pub); log.Fail(e) { - return e - } - zr.PubKey = pub - } else { - return e + zr := event.T{ + PubKey: pub, + Tags: tags.Tags{}, } - zr.Tags = zr.Tags.AppendUnique(tags.Tag{"amount", fmt.Sprint(amount * 1000)}) - relays := tags.Tag{"relays"} + rls := tags.Tag{"relays"} for k, v := range cfg.Relays { if v.Write { - relays = append(relays, k) + rls = append(rls, k) } } - zr.Tags = zr.Tags.AppendUnique(relays) + zr.Tags = zr.Tags.AppendUnique(rls) var prefix string + var s any if prefix, s, e = nip19.Decode(cCtx.Args().First()); !log.Fail(e) { switch prefix { case "nevent": @@ -203,23 +193,22 @@ func doZap(cCtx *cli.Context) (e error) { return errors.New("invalid argument") } } - zr.Kind = event.KindZapRequest // 9734 zr.CreatedAt = timestamp.Now() zr.Content = comment - if e := zr.Sign(sk); log.Fail(e) { + if e = zr.Sign(sk); log.Fail(e) { return e } - b, e := zr.MarshalJSON() - if log.Fail(e) { + var b []byte + if b, e = zr.MarshalJSON(); log.Fail(e) { return e } - - zi, e := cfg.ZapInfo(receipt) - if log.Fail(e) { + var zi *Lnurlp + if zi, e = cfg.ZapInfo(receipt); log.Fail(e) { return e } - u, e := url.Parse(zi.Callback) + var u *url.URL + u, e = url.Parse(zi.Callback) if log.Fail(e) { return e } @@ -227,18 +216,15 @@ func doZap(cCtx *cli.Context) (e error) { param.Set("amount", fmt.Sprint(amount*1000)) param.Set("nostr", string(b)) u.RawQuery = param.Encode() - resp, e := http.Get(u.String()) - if log.Fail(e) { + var resp *http.Response + if resp, e = http.Get(u.String()); log.Fail(e) { return e } - defer resp.Body.Close() - + defer log.Fail(resp.Body.Close()) var iv Invoice - e = json.NewDecoder(resp.Body).Decode(&iv) - if log.Fail(e) { + if e = json.NewDecoder(resp.Body).Decode(&iv); log.Fail(e) { return e } - if cfg.NwcURI == "" { config := qrterminal.Config{ HalfBlocks: false, @@ -252,7 +238,7 @@ func doZap(cCtx *cli.Context) (e error) { fmt.Println("lightning:" + iv.PR) qrterminal.GenerateWithConfig("lightning:"+iv.PR, config) } else { - pay(cCtx.App.Metadata["config"].(*C), iv.PR) + log.Fail(pay(cCtx.App.Metadata["config"].(*C), iv.PR)) } return nil } diff --git a/pkg/go-nostr/nip04/nip04.go b/pkg/go-nostr/nip04/nip04.go index 27cd02a3..89403acf 100644 --- a/pkg/go-nostr/nip04/nip04.go +++ b/pkg/go-nostr/nip04/nip04.go @@ -11,29 +11,31 @@ import ( "github.com/Hubmakerlabs/replicatr/pkg/ec" "github.com/Hubmakerlabs/replicatr/pkg/hex" + log2 "github.com/Hubmakerlabs/replicatr/pkg/log" ) +var log = log2.GetStd() + // ComputeSharedSecret returns a shared secret key used to encrypt messages. // The private and public keys should be hex encoded. // Uses the Diffie-Hellman key exchange (ECDH) (RFC 4753). -func ComputeSharedSecret(pub string, sk string) (sharedSecret []byte, e error) { - privKeyBytes, e := hex.Dec(sk) +func ComputeSharedSecret(pub string, sec string) (sharedSecret []byte, e error) { + var secBytes []byte + secBytes, e = hex.Dec(sec) if e != nil { return nil, fmt.Errorf("error decoding sender private key: %w", e) } - privKey, _ := btcec.PrivKeyFromBytes(privKeyBytes) - + secKey, _ := btcec.PrivKeyFromBytes(secBytes) // adding 02 to signal that this is a compressed public key (33 bytes) - pubKeyBytes, e := hex.Dec("02" + pub) - if e != nil { + var pubBytes []byte + if pubBytes, e = hex.Dec("02" + pub); log.Fail(e) { return nil, fmt.Errorf("error decoding hex string of receiver public key '%s': %w", "02"+pub, e) } - pubKey, e := btcec.ParsePubKey(pubKeyBytes) - if e != nil { + var pubKey *btcec.PublicKey + if pubKey, e = btcec.ParsePubKey(pubBytes); log.Fail(e) { return nil, fmt.Errorf("error parsing receiver public key '%s': %w", "02"+pub, e) } - - return btcec.GenerateSharedSecret(privKey, pubKey), nil + return btcec.GenerateSharedSecret(secKey, pubKey), nil } // Encrypt encrypts message with key using aes-256-cbc.