mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-09 00:58: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)
|
||||
}
|
||||
29
event.go
29
event.go
@@ -154,21 +154,20 @@ example:
|
||||
|
||||
doAuth := c.Bool("auth")
|
||||
|
||||
// then process input and generate events
|
||||
for stdinEvent := range getStdinLinesOrBlank() {
|
||||
evt := nostr.Event{
|
||||
Tags: make(nostr.Tags, 0, 3),
|
||||
}
|
||||
// then process input and generate events:
|
||||
|
||||
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
|
||||
|
||||
if stdinEvent != "" {
|
||||
if err := easyjson.Unmarshal([]byte(stdinEvent), &evt); err != nil {
|
||||
ctx = lineProcessingError(ctx, "invalid event received from stdin: %s", err)
|
||||
continue
|
||||
}
|
||||
kindWasSupplied = strings.Contains(stdinEvent, `"kind"`)
|
||||
return fmt.Errorf("invalid event received from stdin: %s", err)
|
||||
}
|
||||
|
||||
if kind := c.Uint("kind"); slices.Contains(c.FlagNames(), "kind") {
|
||||
@@ -324,6 +323,14 @@ example:
|
||||
log(nevent + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for stdinEvent := range getJsonsOrBlank() {
|
||||
if err := handleEvent(stdinEvent); err != nil {
|
||||
ctx = lineProcessingError(ctx, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
exitIfLineProcessingError(ctx)
|
||||
|
||||
72
helpers.go
72
helpers.go
@@ -4,11 +4,13 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -43,59 +45,71 @@ func isPiped() bool {
|
||||
return stat.Mode()&os.ModeCharDevice == 0
|
||||
}
|
||||
|
||||
func getStdinLinesOrBlank() chan string {
|
||||
multi := make(chan string)
|
||||
if hasStdinLines := writeStdinLinesOrNothing(multi); !hasStdinLines {
|
||||
single := make(chan string, 1)
|
||||
single <- ""
|
||||
close(single)
|
||||
return single
|
||||
} else {
|
||||
return multi
|
||||
func getJsonsOrBlank() iter.Seq[string] {
|
||||
var curr strings.Builder
|
||||
|
||||
return func(yield func(string) bool) {
|
||||
for stdinLine := range getStdinLinesOrBlank() {
|
||||
// we're look for an event, but it may be in multiple lines, so if json parsing fails
|
||||
// we'll try the next line until we're successful
|
||||
curr.WriteString(stdinLine)
|
||||
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())
|
||||
}
|
||||
|
||||
func getStdinLinesOrArgumentsFromSlice(args []string) chan string {
|
||||
func getStdinLinesOrArgumentsFromSlice(args []string) iter.Seq[string] {
|
||||
// try the first argument
|
||||
if len(args) > 0 {
|
||||
argsCh := make(chan string, 1)
|
||||
go func() {
|
||||
for _, arg := range args {
|
||||
argsCh <- arg
|
||||
}
|
||||
close(argsCh)
|
||||
}()
|
||||
return argsCh
|
||||
return slices.Values(args)
|
||||
}
|
||||
|
||||
// try the stdin
|
||||
multi := make(chan string)
|
||||
if !writeStdinLinesOrNothing(multi) {
|
||||
close(multi)
|
||||
return func(yield func(string) bool) {
|
||||
writeStdinLinesOrNothing(yield)
|
||||
}
|
||||
return multi
|
||||
}
|
||||
|
||||
func writeStdinLinesOrNothing(ch chan string) (hasStdinLines bool) {
|
||||
func writeStdinLinesOrNothing(yield func(string) bool) (hasStdinLines bool) {
|
||||
if isPiped() {
|
||||
// piped
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Buffer(make([]byte, 16*1024*1024), 256*1024*1024)
|
||||
hasEmittedAtLeastOne := false
|
||||
for scanner.Scan() {
|
||||
ch <- strings.TrimSpace(scanner.Text())
|
||||
if !yield(strings.TrimSpace(scanner.Text())) {
|
||||
return
|
||||
}
|
||||
hasEmittedAtLeastOne = true
|
||||
}
|
||||
if !hasEmittedAtLeastOne {
|
||||
ch <- ""
|
||||
yield("")
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
return true
|
||||
} else {
|
||||
// not piped
|
||||
|
||||
11
main.go
11
main.go
@@ -39,6 +39,7 @@ var app = &cli.Command{
|
||||
outbox,
|
||||
wallet,
|
||||
mcpServer,
|
||||
curl,
|
||||
},
|
||||
Version: version,
|
||||
Flags: []cli.Flag{
|
||||
@@ -140,6 +141,16 @@ func main() {
|
||||
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 {
|
||||
stdout(err)
|
||||
colors.reset()
|
||||
|
||||
Reference in New Issue
Block a user