package main import ( "context" "fmt" "io" "log" "mime" "net/http" "os" "path/filepath" "strings" "git.sr.ht/~emersion/gqlclient" "github.com/dustin/go-humanize" "github.com/spf13/cobra" "git.sr.ht/~xenrox/hut/srht/pastesrht" "git.sr.ht/~xenrox/hut/termfmt" ) func newPasteCommand() *cobra.Command { cmd := &cobra.Command{ Use: "paste", Short: "Use the paste API", } cmd.AddCommand(newPasteCreateCommand()) cmd.AddCommand(newPasteDeleteCommand()) cmd.AddCommand(newPasteListCommand()) cmd.AddCommand(newPasteShowCommand()) cmd.AddCommand(newPasteUpdateCommand()) cmd.AddCommand(newPasteUserWebhookCommand()) return cmd } func newPasteCreateCommand() *cobra.Command { var visibility string var name string run := func(cmd *cobra.Command, args []string) { ctx := cmd.Context() pasteVisibility, err := pastesrht.ParseVisibility(visibility) if err != nil { log.Fatal(err) } c := createClient("paste", cmd) if name != "" && len(args) > 0 { log.Fatalln("--name is only supported when reading from stdin") } var files []gqlclient.Upload for _, filename := range args { f, err := os.Open(filename) if err != nil { log.Fatalf("failed to open input file: %v", err) } defer f.Close() t := mime.TypeByExtension(filename) if t == "" { t = "text/plain" } files = append(files, gqlclient.Upload{ Filename: filepath.Base(filename), MIMEType: t, Body: f, }) } if len(args) == 0 { files = append(files, gqlclient.Upload{ Filename: name, MIMEType: "text/plain", Body: os.Stdin, }) } paste, err := pastesrht.CreatePaste(c.Client, ctx, files, pasteVisibility) if err != nil { log.Fatal(err) } if termfmt.IsTerminal() { log.Printf("Created paste %v/%v/%v", c.BaseURL, paste.User.CanonicalName, paste.Id) } else { fmt.Printf("%v/%v/%v\n", c.BaseURL, paste.User.CanonicalName, paste.Id) } } cmd := &cobra.Command{ Use: "create [filenames...]", Short: "Create a new paste", Run: run, } cmd.Flags().StringVarP(&visibility, "visibility", "v", "unlisted", "paste visibility") cmd.RegisterFlagCompletionFunc("visibility", completeVisibility) cmd.Flags().StringVarP(&name, "name", "n", "", "paste name (when reading from stdin)") return cmd } func newPasteDeleteCommand() *cobra.Command { run := func(cmd *cobra.Command, args []string) { ctx := cmd.Context() for _, arg := range args { id, _, instance := parseResourceName(arg) c := createClientWithInstance("paste", cmd, instance) paste, err := pastesrht.Delete(c.Client, ctx, id) if err != nil { log.Fatalf("failed to delete paste %s: %v", id, err) } if paste == nil { log.Printf("Paste %s does not exist\n", id) } else { log.Printf("Deleted paste %s\n", paste.Id) } } } cmd := &cobra.Command{ Use: "delete ", Short: "Delete pastes", Args: cobra.MinimumNArgs(1), ValidArgsFunction: completePasteID, Run: run, } return cmd } func newPasteListCommand() *cobra.Command { run := func(cmd *cobra.Command, args []string) { ctx := cmd.Context() c := createClient("paste", cmd) var cursor *pastesrht.Cursor err := pagerify(func(p pager) error { pastes, err := pastesrht.Pastes(c.Client, ctx, cursor) if err != nil { return err } for _, paste := range pastes.Results { printPaste(p, &paste) fmt.Fprintln(p) } cursor = pastes.Cursor if cursor == nil { return pagerDone } return nil }) if err != nil { log.Fatal(err) } } cmd := &cobra.Command{ Use: "list", Short: "List pastes", Run: run, } return cmd } func printPaste(w io.Writer, paste *pastesrht.Paste) { fmt.Fprintf(w, "%s %s %s\n", termfmt.DarkYellow.Sprint(paste.Id), paste.Visibility.TermString(), humanize.Time(paste.Created.Time)) for _, file := range paste.Files { if file.Filename != nil && *file.Filename != "" { fmt.Fprintf(w, " %s\n", *file.Filename) } } } func newPasteShowCommand() *cobra.Command { cmd := &cobra.Command{ Use: "show ", Short: "Display a paste", Args: cobra.ExactArgs(1), ValidArgsFunction: completePasteID, } cmd.Run = func(cmd *cobra.Command, args []string) { ctx := cmd.Context() id, _, instance := parseResourceName(args[0]) c := createClientWithInstance("paste", cmd, instance) paste, err := pastesrht.ShowPaste(c.Client, ctx, id) if err != nil { log.Fatal(err) } else if paste == nil { log.Fatalf("Paste %q does not exist", id) } fmt.Printf("%s %s %s\n", termfmt.DarkYellow.Sprint(paste.Id), paste.Visibility.TermString(), humanize.Time(paste.Created.Time)) for _, file := range paste.Files { fmt.Print("\n■ ") if file.Filename != nil && *file.Filename != "" { fmt.Println(termfmt.Bold.String(*file.Filename)) } else { fmt.Println(termfmt.Dim.String("(untitled)")) } fmt.Println() fetchPasteFile(ctx, c.HTTP, &file) } } return cmd } func fetchPasteFile(ctx context.Context, c *http.Client, file *pastesrht.File) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, string(file.Contents), nil) if err != nil { log.Fatalf("Failed to create request to fetch file: %v", err) } resp, err := c.Do(req) if err != nil { log.Fatalf("Failed to fetch file: %v", err) } defer resp.Body.Close() if _, err := io.Copy(os.Stdout, resp.Body); err != nil { log.Fatalf("Failed to copy to stdout: %v", err) } } func newPasteUpdateCommand() *cobra.Command { var visibility string run := func(cmd *cobra.Command, args []string) { ctx := cmd.Context() c := createClient("paste", cmd) pasteVisibility, err := pastesrht.ParseVisibility(visibility) if err != nil { log.Fatal(err) } paste, err := pastesrht.Update(c.Client, ctx, args[0], pasteVisibility) if err != nil { log.Fatal(err) } if paste == nil { log.Fatalf("Paste %s does not exist\n", args[0]) } log.Printf("Updated paste %s visibility to %s\n", paste.Id, pasteVisibility) } cmd := &cobra.Command{ Use: "update ", Short: "Update a paste's visibility", Args: cobra.ExactArgs(1), ValidArgsFunction: completePasteID, Run: run, } cmd.Flags().StringVarP(&visibility, "visibility", "v", "", "paste visibility") cmd.MarkFlagRequired("visibility") cmd.RegisterFlagCompletionFunc("visibility", completeVisibility) return cmd } func newPasteUserWebhookCommand() *cobra.Command { cmd := &cobra.Command{ Use: "user-webhook", Short: "Manage user webhooks", } cmd.AddCommand(newPasteUserWebhookCreateCommand()) cmd.AddCommand(newPasteUserWebhookListCommand()) cmd.AddCommand(newPasteUserWebhookDeleteCommand()) return cmd } func newPasteUserWebhookCreateCommand() *cobra.Command { var events []string var stdin bool var url string run := func(cmd *cobra.Command, args []string) { ctx := cmd.Context() c := createClient("paste", cmd) var config pastesrht.UserWebhookInput config.Url = url whEvents, err := pastesrht.ParseEvents(events) if err != nil { log.Fatal(err) } config.Events = whEvents config.Query = readWebhookQuery(stdin) webhook, err := pastesrht.CreateUserWebhook(c.Client, ctx, config) if err != nil { log.Fatal(err) } log.Printf("Created user webhook with ID %d\n", webhook.Id) } cmd := &cobra.Command{ Use: "create", Short: "Create a user webhook", Args: cobra.ExactArgs(0), ValidArgsFunction: cobra.NoFileCompletions, Run: run, } cmd.Flags().StringSliceVarP(&events, "events", "e", nil, "webhook events") cmd.RegisterFlagCompletionFunc("events", completePasteUserWebhookEvents) cmd.MarkFlagRequired("events") cmd.Flags().BoolVar(&stdin, "stdin", false, "read webhook query from stdin") cmd.Flags().StringVarP(&url, "url", "u", "", "payload url") cmd.RegisterFlagCompletionFunc("url", cobra.NoFileCompletions) cmd.MarkFlagRequired("url") return cmd } func newPasteUserWebhookListCommand() *cobra.Command { run := func(cmd *cobra.Command, args []string) { ctx := cmd.Context() c := createClient("paste", cmd) var cursor *pastesrht.Cursor err := pagerify(func(p pager) error { webhooks, err := pastesrht.UserWebhooks(c.Client, ctx, cursor) if err != nil { return err } for _, webhook := range webhooks.Results { fmt.Fprintf(p, "%s %s\n", termfmt.DarkYellow.Sprintf("#%d", webhook.Id), webhook.Url) } cursor = webhooks.Cursor if cursor == nil { return pagerDone } return nil }) if err != nil { log.Fatal(err) } } cmd := &cobra.Command{ Use: "list", Short: "List user webhooks", Args: cobra.ExactArgs(0), Run: run, } return cmd } func newPasteUserWebhookDeleteCommand() *cobra.Command { run := func(cmd *cobra.Command, args []string) { ctx := cmd.Context() c := createClient("paste", cmd) id, err := parseInt32(args[0]) if err != nil { log.Fatal(err) } webhook, err := pastesrht.DeleteUserWebhook(c.Client, ctx, id) if err != nil { log.Fatal(err) } log.Printf("Deleted webhook %d\n", webhook.Id) } cmd := &cobra.Command{ Use: "delete ", Short: "Delete a user webhook", Args: cobra.ExactArgs(1), ValidArgsFunction: completePasteUserWebhookID, Run: run, } return cmd } func completePasteID(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ctx := cmd.Context() c := createClient("paste", cmd) var pasteList []string pastes, err := pastesrht.PasteCompletionList(c.Client, ctx) if err != nil { return nil, cobra.ShellCompDirectiveNoFileComp } for _, paste := range pastes.Results { if cmd.Name() == "delete" && hasCmdArg(cmd, paste.Id) { continue } str := paste.Id var files string for i, file := range paste.Files { if file.Filename != nil && *file.Filename != "" { if i != 0 { files += ", " } files += *file.Filename } } if files != "" { str += fmt.Sprintf("\t%s", files) } pasteList = append(pasteList, str) } return pasteList, cobra.ShellCompDirectiveNoFileComp } func completePasteUserWebhookEvents(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var eventList []string events := [3]string{"paste_created", "paste_updated", "paste_deleted"} set := strings.ToLower(cmd.Flag("events").Value.String()) for _, event := range events { if !strings.Contains(set, event) { eventList = append(eventList, event) } } return eventList, cobra.ShellCompDirectiveNoFileComp } func completePasteUserWebhookID(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ctx := cmd.Context() c := createClient("paste", cmd) var webhookList []string webhooks, err := pastesrht.UserWebhooks(c.Client, ctx, nil) if err != nil { return nil, cobra.ShellCompDirectiveNoFileComp } for _, webhook := range webhooks.Results { s := fmt.Sprintf("%d\t%s", webhook.Id, webhook.Url) webhookList = append(webhookList, s) } return webhookList, cobra.ShellCompDirectiveNoFileComp }