mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-09 09:08:50 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d00976a669 | ||
|
|
4392293ed6 | ||
|
|
60d1292f80 | ||
|
|
6c634d8081 |
131
curl.go
Normal file
131
curl.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
|
"github.com/nbd-wtf/go-nostr"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
var curlFlags []string
|
||||||
|
|
||||||
|
var curl = &cli.Command{
|
||||||
|
Name: "curl",
|
||||||
|
Usage: "calls curl but with a nip98 header",
|
||||||
|
Description: "accepts all flags and arguments exactly as they would be passed to curl.",
|
||||||
|
Flags: defaultKeyFlags,
|
||||||
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
|
kr, _, err := gatherKeyerFromArguments(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// cowboy parsing of curl flags to get the data we need for nip98
|
||||||
|
var url string
|
||||||
|
var method string
|
||||||
|
var presumedMethod string
|
||||||
|
|
||||||
|
curlBodyBuildingFlags := []string{
|
||||||
|
"-d",
|
||||||
|
"--data",
|
||||||
|
"--data-binary",
|
||||||
|
"--data-ascii",
|
||||||
|
"--data-raw",
|
||||||
|
"--data-urlencode",
|
||||||
|
"-F",
|
||||||
|
"--form",
|
||||||
|
"--form-string",
|
||||||
|
"--form-escape",
|
||||||
|
"--upload-file",
|
||||||
|
}
|
||||||
|
|
||||||
|
nextIsMethod := false
|
||||||
|
for _, f := range curlFlags {
|
||||||
|
if nextIsMethod {
|
||||||
|
method = f
|
||||||
|
method, _ = strings.CutPrefix(method, `"`)
|
||||||
|
method, _ = strings.CutSuffix(method, `"`)
|
||||||
|
method = strings.ToUpper(method)
|
||||||
|
} else if strings.HasPrefix(f, "https://") || strings.HasPrefix(f, "http://") {
|
||||||
|
url = f
|
||||||
|
} else if f == "--request" || f == "-X" {
|
||||||
|
nextIsMethod = true
|
||||||
|
continue
|
||||||
|
} else if slices.Contains(curlBodyBuildingFlags, f) ||
|
||||||
|
slices.ContainsFunc(curlBodyBuildingFlags, func(s string) bool {
|
||||||
|
return strings.HasPrefix(f, s)
|
||||||
|
}) {
|
||||||
|
presumedMethod = "POST"
|
||||||
|
}
|
||||||
|
nextIsMethod = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if url == "" {
|
||||||
|
return fmt.Errorf("can't create nip98 event: target url is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if method == "" {
|
||||||
|
if presumedMethod != "" {
|
||||||
|
method = presumedMethod
|
||||||
|
} else {
|
||||||
|
method = "GET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make and sign event
|
||||||
|
evt := nostr.Event{
|
||||||
|
Kind: 27235,
|
||||||
|
CreatedAt: nostr.Now(),
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
{"u", url},
|
||||||
|
{"method", method},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := kr.SignEvent(ctx, &evt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// the first 2 indexes of curlFlags were reserved for this
|
||||||
|
curlFlags[0] = "-H"
|
||||||
|
curlFlags[1] = fmt.Sprintf("Authorization: Nostr %s", base64.StdEncoding.EncodeToString([]byte(evt.String())))
|
||||||
|
|
||||||
|
// call curl
|
||||||
|
cmd := exec.Command("curl", curlFlags...)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Run()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func realCurl() error {
|
||||||
|
curlFlags = make([]string, 2, max(len(os.Args)-4, 2))
|
||||||
|
keyFlags := make([]string, 0, 5)
|
||||||
|
|
||||||
|
for i := 0; i < len(os.Args[2:]); i++ {
|
||||||
|
arg := os.Args[i+2]
|
||||||
|
if slices.ContainsFunc(defaultKeyFlags, func(f cli.Flag) bool {
|
||||||
|
bareArg, _ := strings.CutPrefix(arg, "-")
|
||||||
|
bareArg, _ = strings.CutPrefix(bareArg, "-")
|
||||||
|
return slices.Contains(f.Names(), bareArg)
|
||||||
|
}) {
|
||||||
|
keyFlags = append(keyFlags, arg)
|
||||||
|
if arg != "--prompt-sec" {
|
||||||
|
i++
|
||||||
|
val := os.Args[i+2]
|
||||||
|
keyFlags = append(keyFlags, val)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
curlFlags = append(curlFlags, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return curl.Run(context.Background(), keyFlags)
|
||||||
|
}
|
||||||
31
event.go
31
event.go
@@ -154,21 +154,20 @@ example:
|
|||||||
|
|
||||||
doAuth := c.Bool("auth")
|
doAuth := c.Bool("auth")
|
||||||
|
|
||||||
// then process input and generate events
|
// then process input and generate events:
|
||||||
for stdinEvent := range getStdinLinesOrBlank() {
|
|
||||||
evt := nostr.Event{
|
|
||||||
Tags: make(nostr.Tags, 0, 3),
|
|
||||||
}
|
|
||||||
|
|
||||||
kindWasSupplied := false
|
// will reuse this
|
||||||
|
var evt nostr.Event
|
||||||
|
|
||||||
|
// this is called when we have a valid json from stdin
|
||||||
|
handleEvent := func(stdinEvent string) error {
|
||||||
|
evt.Content = ""
|
||||||
|
|
||||||
|
kindWasSupplied := strings.Contains(stdinEvent, `"kind"`)
|
||||||
mustRehashAndResign := false
|
mustRehashAndResign := false
|
||||||
|
|
||||||
if stdinEvent != "" {
|
if err := easyjson.Unmarshal([]byte(stdinEvent), &evt); err != nil {
|
||||||
if err := easyjson.Unmarshal([]byte(stdinEvent), &evt); err != nil {
|
return fmt.Errorf("invalid event received from stdin: %s", err)
|
||||||
ctx = lineProcessingError(ctx, "invalid event received from stdin: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
kindWasSupplied = strings.Contains(stdinEvent, `"kind"`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind := c.Uint("kind"); slices.Contains(c.FlagNames(), "kind") {
|
if kind := c.Uint("kind"); slices.Contains(c.FlagNames(), "kind") {
|
||||||
@@ -324,6 +323,14 @@ example:
|
|||||||
log(nevent + "\n")
|
log(nevent + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for stdinEvent := range getJsonsOrBlank() {
|
||||||
|
if err := handleEvent(stdinEvent); err != nil {
|
||||||
|
ctx = lineProcessingError(ctx, err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exitIfLineProcessingError(ctx)
|
exitIfLineProcessingError(ctx)
|
||||||
|
|||||||
86
helpers.go
86
helpers.go
@@ -4,11 +4,13 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"iter"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -43,59 +45,71 @@ func isPiped() bool {
|
|||||||
return stat.Mode()&os.ModeCharDevice == 0
|
return stat.Mode()&os.ModeCharDevice == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStdinLinesOrBlank() chan string {
|
func getJsonsOrBlank() iter.Seq[string] {
|
||||||
multi := make(chan string)
|
var curr strings.Builder
|
||||||
if hasStdinLines := writeStdinLinesOrNothing(multi); !hasStdinLines {
|
|
||||||
single := make(chan string, 1)
|
return func(yield func(string) bool) {
|
||||||
single <- ""
|
for stdinLine := range getStdinLinesOrBlank() {
|
||||||
close(single)
|
// we're look for an event, but it may be in multiple lines, so if json parsing fails
|
||||||
return single
|
// we'll try the next line until we're successful
|
||||||
} else {
|
curr.WriteString(stdinLine)
|
||||||
return multi
|
stdinEvent := curr.String()
|
||||||
|
|
||||||
|
var dummy any
|
||||||
|
if err := json.Unmarshal([]byte(stdinEvent), &dummy); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !yield(stdinEvent) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
curr.Reset()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStdinLinesOrArguments(args cli.Args) chan string {
|
func getStdinLinesOrBlank() iter.Seq[string] {
|
||||||
|
return func(yield func(string) bool) {
|
||||||
|
if hasStdinLines := writeStdinLinesOrNothing(yield); !hasStdinLines {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStdinLinesOrArguments(args cli.Args) iter.Seq[string] {
|
||||||
return getStdinLinesOrArgumentsFromSlice(args.Slice())
|
return getStdinLinesOrArgumentsFromSlice(args.Slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStdinLinesOrArgumentsFromSlice(args []string) chan string {
|
func getStdinLinesOrArgumentsFromSlice(args []string) iter.Seq[string] {
|
||||||
// try the first argument
|
// try the first argument
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
argsCh := make(chan string, 1)
|
return slices.Values(args)
|
||||||
go func() {
|
|
||||||
for _, arg := range args {
|
|
||||||
argsCh <- arg
|
|
||||||
}
|
|
||||||
close(argsCh)
|
|
||||||
}()
|
|
||||||
return argsCh
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// try the stdin
|
// try the stdin
|
||||||
multi := make(chan string)
|
return func(yield func(string) bool) {
|
||||||
if !writeStdinLinesOrNothing(multi) {
|
writeStdinLinesOrNothing(yield)
|
||||||
close(multi)
|
|
||||||
}
|
}
|
||||||
return multi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeStdinLinesOrNothing(ch chan string) (hasStdinLines bool) {
|
func writeStdinLinesOrNothing(yield func(string) bool) (hasStdinLines bool) {
|
||||||
if isPiped() {
|
if isPiped() {
|
||||||
// piped
|
// piped
|
||||||
go func() {
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
scanner.Buffer(make([]byte, 16*1024*1024), 256*1024*1024)
|
||||||
scanner.Buffer(make([]byte, 16*1024*1024), 256*1024*1024)
|
hasEmittedAtLeastOne := false
|
||||||
hasEmittedAtLeastOne := false
|
for scanner.Scan() {
|
||||||
for scanner.Scan() {
|
if !yield(strings.TrimSpace(scanner.Text())) {
|
||||||
ch <- strings.TrimSpace(scanner.Text())
|
return
|
||||||
hasEmittedAtLeastOne = true
|
|
||||||
}
|
}
|
||||||
if !hasEmittedAtLeastOne {
|
hasEmittedAtLeastOne = true
|
||||||
ch <- ""
|
}
|
||||||
}
|
if !hasEmittedAtLeastOne {
|
||||||
close(ch)
|
yield("")
|
||||||
}()
|
}
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
// not piped
|
// not piped
|
||||||
|
|||||||
11
main.go
11
main.go
@@ -39,6 +39,7 @@ var app = &cli.Command{
|
|||||||
outbox,
|
outbox,
|
||||||
wallet,
|
wallet,
|
||||||
mcpServer,
|
mcpServer,
|
||||||
|
curl,
|
||||||
},
|
},
|
||||||
Version: version,
|
Version: version,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
@@ -140,6 +141,16 @@ func main() {
|
|||||||
Usage: "prints the version",
|
Usage: "prints the version",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a megahack to enable this curl command proxy
|
||||||
|
if len(os.Args) > 2 && os.Args[1] == "curl" {
|
||||||
|
if err := realCurl(); err != nil {
|
||||||
|
stdout(err)
|
||||||
|
colors.reset()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := app.Run(context.Background(), os.Args); err != nil {
|
if err := app.Run(context.Background(), os.Args); err != nil {
|
||||||
stdout(err)
|
stdout(err)
|
||||||
colors.reset()
|
colors.reset()
|
||||||
|
|||||||
2
req.go
2
req.go
@@ -107,7 +107,7 @@ example:
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
for stdinFilter := range getStdinLinesOrBlank() {
|
for stdinFilter := range getJsonsOrBlank() {
|
||||||
filter := nostr.Filter{}
|
filter := nostr.Filter{}
|
||||||
if stdinFilter != "" {
|
if stdinFilter != "" {
|
||||||
if err := easyjson.Unmarshal([]byte(stdinFilter), &filter); err != nil {
|
if err := easyjson.Unmarshal([]byte(stdinFilter), &filter); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user