119 lines
2.6 KiB
Go

package cli
import (
"flag"
"fmt"
"io"
"os"
"strings"
"time"
"gitea.pglikers.com/tools/slacksend/internal/slack"
"gitea.pglikers.com/tools/slacksend/internal/version"
)
type Options struct {
ShowVersion bool
WebhookURL string
TimeoutSec int
Title string
}
func Run(args []string) int {
return run(args, os.Stdin, os.Stdout, os.Stderr)
}
func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
fs := flag.NewFlagSet("slacksend", flag.ContinueOnError)
fs.SetOutput(stderr)
fs.Usage = func() { Usage(stdout) }
var opt Options
fs.BoolVar(&opt.ShowVersion, "version", false, "show version")
fs.BoolVar(&opt.ShowVersion, "V", false, "show version (short)")
// other options
fs.StringVar(&opt.WebhookURL, "webhook", "", "Slack Incoming Webhook URL (or env SLACK_WEBHOOK_URL)")
fs.IntVar(&opt.TimeoutSec, "timeout", 10, "HTTP timeout seconds")
fs.StringVar(&opt.Title, "title", "", "message title (shown as bold)")
if err := fs.Parse(args[1:]); err != nil {
if err == flag.ErrHelp {
Usage(stdout)
return 0
}
fmt.Fprintln(stderr, err)
Usage(stderr)
return 2
}
if opt.ShowVersion {
fmt.Fprintln(stdout, version.String())
return 0
}
if len(fs.Args()) == 0 && isTerminal(stdin) {
fmt.Fprintln(stderr, "message is required. provide args or pipe via stdin")
Usage(stderr)
return 2
}
// webhook URL の解決: flag > env
webhookURL := strings.TrimSpace(opt.WebhookURL)
if webhookURL == "" {
webhookURL = strings.TrimSpace(os.Getenv("SLACK_WEBHOOK_URL"))
}
if webhookURL == "" {
fmt.Fprintln(stderr, "Slack webhook URL is required. Set --webhook or SLACK_WEBHOOK_URL.")
return 2
}
msg, err := message(fs.Args(), stdin)
if err != nil {
fmt.Fprintln(stderr, "read message error:", err)
return 1
}
if msg == "" {
Usage(stderr)
return 2
}
client := slack.WebhookClient{
URL: webhookURL,
Timeout: time.Duration(opt.TimeoutSec) * time.Second,
}
if err := client.Send(opt.Title, msg); err != nil {
fmt.Fprintln(stderr, "send failed:", err)
return 1
}
fmt.Fprintln(stdout, "ok")
return 0
}
func message(args []string, stdin io.Reader) (string, error) {
if len(args) > 0 {
return strings.Join(args, " "), nil
}
b, err := io.ReadAll(stdin)
if err != nil {
return "", err
}
return strings.TrimSpace(string(b)), nil
}
// isTerminal reports whether the given reader is a character device (e.g., TTY).
func isTerminal(r io.Reader) bool {
fd, ok := r.(interface {
Stat() (os.FileInfo, error)
})
if !ok {
return false
}
info, err := fd.Stat()
if err != nil {
return false
}
return info.Mode()&os.ModeCharDevice != 0
}