mirror of
https://github.com/fiatjaf/nak.git
synced 2026-02-13 19:34:35 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
037e8efcc6 | ||
|
|
1b380dea9a | ||
|
|
f126b3f7ee | ||
|
|
dc5ffe5129 |
@@ -418,6 +418,13 @@ var bunker = &cli.Command{
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if slices.Contains(authorizedSecrets, secret) {
|
if slices.Contains(authorizedSecrets, secret) {
|
||||||
|
// add client to authorized list for subsequent requests
|
||||||
|
if !slices.ContainsFunc(config.Clients, func(c BunkerConfigClient) bool { return c.PubKey == from }) {
|
||||||
|
config.Clients = append(config.Clients, BunkerConfigClient{PubKey: from})
|
||||||
|
if persist != nil {
|
||||||
|
persist()
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
183
count.go
183
count.go
@@ -9,63 +9,17 @@ import (
|
|||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/nip45"
|
"fiatjaf.com/nostr/nip45"
|
||||||
"fiatjaf.com/nostr/nip45/hyperloglog"
|
"fiatjaf.com/nostr/nip45/hyperloglog"
|
||||||
|
"github.com/mailru/easyjson"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var count = &cli.Command{
|
var count = &cli.Command{
|
||||||
Name: "count",
|
Name: "count",
|
||||||
Usage: "generates encoded COUNT messages and optionally use them to talk to relays",
|
Usage: "generates encoded COUNT messages and optionally use them to talk to relays",
|
||||||
Description: `outputs a nip45 request (the flags are mostly the same as 'nak req').`,
|
Description: `like 'nak req', but does a "COUNT" call instead. Will attempt to perform HyperLogLog aggregation if more than one relay is specified.`,
|
||||||
DisableSliceFlagSeparator: true,
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: reqFilterFlags,
|
||||||
&PubKeySliceFlag{
|
ArgsUsage: "[relay...]",
|
||||||
Name: "author",
|
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "only accept events from these authors",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.IntSliceFlag{
|
|
||||||
Name: "kind",
|
|
||||||
Aliases: []string{"k"},
|
|
||||||
Usage: "only accept events with these kind numbers",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "tag",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "takes a tag like -t e=<id>, only accept events with these tags",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "e",
|
|
||||||
Usage: "shortcut for --tag e=<value>",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "p",
|
|
||||||
Usage: "shortcut for --tag p=<value>",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&NaturalTimeFlag{
|
|
||||||
Name: "since",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Usage: "only accept events newer than this (unix timestamp)",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&NaturalTimeFlag{
|
|
||||||
Name: "until",
|
|
||||||
Aliases: []string{"u"},
|
|
||||||
Usage: "only accept events older than this (unix timestamp)",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "limit",
|
|
||||||
Aliases: []string{"l"},
|
|
||||||
Usage: "only accept up to this number of events",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ArgsUsage: "[relay...]",
|
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
biggerUrlSize := 0
|
biggerUrlSize := 0
|
||||||
relayUrls := c.Args().Slice()
|
relayUrls := c.Args().Slice()
|
||||||
@@ -84,95 +38,62 @@ var count = &cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filter := nostr.Filter{}
|
// go line by line from stdin or run once with input from flags
|
||||||
|
for stdinFilter := range getJsonsOrBlank() {
|
||||||
if authors := getPubKeySlice(c, "author"); len(authors) > 0 {
|
filter := nostr.Filter{}
|
||||||
filter.Authors = authors
|
if stdinFilter != "" {
|
||||||
}
|
if err := easyjson.Unmarshal([]byte(stdinFilter), &filter); err != nil {
|
||||||
if kinds64 := c.IntSlice("kind"); len(kinds64) > 0 {
|
ctx = lineProcessingError(ctx, "invalid filter '%s' received from stdin: %s", stdinFilter, err)
|
||||||
kinds := make([]nostr.Kind, len(kinds64))
|
|
||||||
for i, v := range kinds64 {
|
|
||||||
kinds[i] = nostr.Kind(v)
|
|
||||||
}
|
|
||||||
filter.Kinds = kinds
|
|
||||||
}
|
|
||||||
|
|
||||||
tags := make([][]string, 0, 5)
|
|
||||||
for _, tagFlag := range c.StringSlice("tag") {
|
|
||||||
spl := strings.SplitN(tagFlag, "=", 2)
|
|
||||||
if len(spl) == 2 {
|
|
||||||
tags = append(tags, []string{spl[0], decodeTagValue(spl[1])})
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("invalid --tag '%s'", tagFlag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, etag := range c.StringSlice("e") {
|
|
||||||
tags = append(tags, []string{"e", decodeTagValue(etag)})
|
|
||||||
}
|
|
||||||
for _, ptag := range c.StringSlice("p") {
|
|
||||||
tags = append(tags, []string{"p", decodeTagValue(ptag)})
|
|
||||||
}
|
|
||||||
if len(tags) > 0 {
|
|
||||||
filter.Tags = make(nostr.TagMap)
|
|
||||||
for _, tag := range tags {
|
|
||||||
if _, ok := filter.Tags[tag[0]]; !ok {
|
|
||||||
filter.Tags[tag[0]] = make([]string, 0, 3)
|
|
||||||
}
|
|
||||||
filter.Tags[tag[0]] = append(filter.Tags[tag[0]], tag[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.IsSet("since") {
|
|
||||||
filter.Since = getNaturalDate(c, "since")
|
|
||||||
}
|
|
||||||
if c.IsSet("until") {
|
|
||||||
filter.Until = getNaturalDate(c, "until")
|
|
||||||
}
|
|
||||||
|
|
||||||
if limit := c.Int("limit"); limit != 0 {
|
|
||||||
filter.Limit = int(limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
successes := 0
|
|
||||||
if len(relayUrls) > 0 {
|
|
||||||
var hll *hyperloglog.HyperLogLog
|
|
||||||
if offset := nip45.HyperLogLogEventPubkeyOffsetForFilter(filter); offset != -1 && len(relayUrls) > 1 {
|
|
||||||
hll = hyperloglog.New(offset)
|
|
||||||
}
|
|
||||||
for _, relayUrl := range relayUrls {
|
|
||||||
relay, _ := sys.Pool.EnsureRelay(relayUrl)
|
|
||||||
count, hllRegisters, err := relay.Count(ctx, filter, nostr.SubscriptionOptions{
|
|
||||||
Label: "nak-count",
|
|
||||||
})
|
|
||||||
fmt.Fprintf(os.Stderr, "%s%s: ", strings.Repeat(" ", biggerUrlSize-len(relayUrl)), relayUrl)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "❌ %s\n", err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var hasHLLStr string
|
if err := applyFlagsToFilter(c, &filter); err != nil {
|
||||||
if hll != nil && len(hllRegisters) == 256 {
|
return err
|
||||||
hll.MergeRegisters(hllRegisters)
|
}
|
||||||
hasHLLStr = " 📋"
|
|
||||||
|
successes := 0
|
||||||
|
if len(relayUrls) > 0 {
|
||||||
|
var hll *hyperloglog.HyperLogLog
|
||||||
|
if offset := nip45.HyperLogLogEventPubkeyOffsetForFilter(filter); offset != -1 && len(relayUrls) > 1 {
|
||||||
|
hll = hyperloglog.New(offset)
|
||||||
}
|
}
|
||||||
|
for _, relayUrl := range relayUrls {
|
||||||
|
relay, _ := sys.Pool.EnsureRelay(relayUrl)
|
||||||
|
count, hllRegisters, err := relay.Count(ctx, filter, nostr.SubscriptionOptions{
|
||||||
|
Label: "nak-count",
|
||||||
|
})
|
||||||
|
fmt.Fprintf(os.Stderr, "%s%s: ", strings.Repeat(" ", biggerUrlSize-len(relayUrl)), relayUrl)
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "%d%s\n", count, hasHLLStr)
|
if err != nil {
|
||||||
successes++
|
fmt.Fprintf(os.Stderr, "error: %s\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasHLLStr string
|
||||||
|
if hll != nil && len(hllRegisters) == 256 {
|
||||||
|
hll.MergeRegisters(hllRegisters)
|
||||||
|
hasHLLStr = " (hll)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "%d%s\n", count, hasHLLStr)
|
||||||
|
successes++
|
||||||
|
}
|
||||||
|
if successes == 0 {
|
||||||
|
return fmt.Errorf("all relays have failed")
|
||||||
|
} else if hll != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "HyperLogLog sum: %d\n", hll.Count())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no relays given, will just print the filter
|
||||||
|
var result string
|
||||||
|
j, _ := json.Marshal([]any{"COUNT", "nak", filter})
|
||||||
|
result = string(j)
|
||||||
|
stdout(result)
|
||||||
}
|
}
|
||||||
if successes == 0 {
|
|
||||||
return fmt.Errorf("all relays have failed")
|
|
||||||
} else if hll != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "📋 HyperLogLog sum: %d\n", hll.Count())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// no relays given, will just print the filter
|
|
||||||
var result string
|
|
||||||
j, _ := json.Marshal([]any{"COUNT", "nak", filter})
|
|
||||||
result = string(j)
|
|
||||||
stdout(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exitIfLineProcessingError(ctx)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
47
mcp.go
47
mcp.go
@@ -158,29 +158,40 @@ var mcpServer = &cli.Command{
|
|||||||
name := required[string](r, "name")
|
name := required[string](r, "name")
|
||||||
limit, _ := optional[float64](r, "limit")
|
limit, _ := optional[float64](r, "limit")
|
||||||
|
|
||||||
filter := nostr.Filter{Search: name, Kinds: []nostr.Kind{0}}
|
|
||||||
if limit > 0 {
|
|
||||||
filter.Limit = int(limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
res := strings.Builder{}
|
res := strings.Builder{}
|
||||||
|
res.Grow(500)
|
||||||
res.WriteString("search results: ")
|
res.WriteString("search results: ")
|
||||||
l := 0
|
|
||||||
for result := range sys.Pool.FetchMany(ctx, []string{"relay.nostr.band", "nostr.wine"}, filter, nostr.SubscriptionOptions{
|
|
||||||
Label: "nak-mcp-search",
|
|
||||||
}) {
|
|
||||||
l++
|
|
||||||
pm, _ := sdk.ParseMetadata(result.Event)
|
|
||||||
res.WriteString(fmt.Sprintf("\n\nResult %d\nUser name: \"%s\"\nPublic key: \"%s\"\nDescription: \"%s\"\n",
|
|
||||||
l, pm.ShortName(), pm.PubKey.Hex(), pm.About))
|
|
||||||
|
|
||||||
if l >= int(limit) {
|
// check if input is already a valid pubkey
|
||||||
break
|
if pubkey, err := nostr.PubKeyFromHex(name); err == nil {
|
||||||
|
pm := sys.FetchProfileMetadata(ctx, pubkey)
|
||||||
|
res.WriteString(fmt.Sprintf("\n\nResult 1\nUser name: \"%s\"\nPublic key: \"%s\"\nDescription: \"%s\"\n",
|
||||||
|
pm.ShortName(), pm.PubKey.Hex(), pm.About))
|
||||||
|
} else {
|
||||||
|
// otherwise try to search
|
||||||
|
filter := nostr.Filter{Search: name, Kinds: []nostr.Kind{0}}
|
||||||
|
if limit > 0 {
|
||||||
|
filter.Limit = int(limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
l := 0
|
||||||
|
for result := range sys.Pool.FetchMany(ctx, []string{"relay.nostr.band", "nostr.wine", "search.nos.social"}, filter, nostr.SubscriptionOptions{
|
||||||
|
Label: "nak-mcp-search",
|
||||||
|
}) {
|
||||||
|
l++
|
||||||
|
pm, _ := sdk.ParseMetadata(result.Event)
|
||||||
|
res.WriteString(fmt.Sprintf("\n\nResult %d\nUser name: \"%s\"\nPublic key: \"%s\"\nDescription: \"%s\"\n",
|
||||||
|
l, pm.ShortName(), pm.PubKey.Hex(), pm.About))
|
||||||
|
|
||||||
|
if l >= int(limit) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l == 0 {
|
||||||
|
return mcp.NewToolResultError("couldn't find anyone with that name."), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if l == 0 {
|
|
||||||
return mcp.NewToolResultError("couldn't find anyone with that name."), nil
|
|
||||||
}
|
|
||||||
return mcp.NewToolResultText(res.String()), nil
|
return mcp.NewToolResultText(res.String()), nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user