-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
177 lines (158 loc) · 4.03 KB
/
main.go
File metadata and controls
177 lines (158 loc) · 4.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package main
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
)
var version = "dev"
const defaultHost = "https://pingrb.com"
const usage = `pingrb sends a push notification to your phone.
Usage:
pingrb configure <token> save your pingrb token from pingrb.com
pingrb configure print the saved token
pingrb <title> [--body BODY] [--url URL]
send a push
Examples:
pingrb configure abc123def456...
pingrb "deploy failed"
pingrb "job done" --body "backfill finished" --url https://example.com/jobs/42
`
func main() {
if err := run(os.Args[1:], os.Stdout); err != nil {
fmt.Fprintln(os.Stderr, "pingrb:", err)
os.Exit(1)
}
}
func run(args []string, stdout io.Writer) error {
if len(args) == 0 {
fmt.Fprint(stdout, usage)
return nil
}
switch args[0] {
case "-h", "--help":
fmt.Fprint(stdout, usage)
return nil
case "-v", "--version":
fmt.Fprintln(stdout, "pingrb", version)
return nil
case "configure":
return runConfigure(args[1:], stdout)
}
return runPing(args)
}
func runConfigure(args []string, stdout io.Writer) error {
if len(args) == 0 {
token, err := readConfig()
if err != nil {
return err
}
fmt.Fprintln(stdout, token)
return nil
}
if len(args) > 1 {
return errors.New("configure takes at most one token argument")
}
if err := writeConfig(args[0]); err != nil {
return err
}
fmt.Fprintln(stdout, "pingrb configured")
return nil
}
func runPing(args []string) error {
title := args[0]
if strings.HasPrefix(title, "-") {
return fmt.Errorf("first argument must be the notification title (got %q)", title)
}
fs := flag.NewFlagSet("pingrb", flag.ContinueOnError)
fs.SetOutput(io.Discard)
body := fs.String("body", "", "notification body")
url := fs.String("url", "", "tap target URL")
if err := fs.Parse(args[1:]); err != nil {
return err
}
token, err := readConfig()
if err != nil {
return err
}
return sendPing(token, title, *body, *url)
}
func configPath() (string, error) {
dir, err := os.UserConfigDir()
if err != nil {
return "", err
}
return filepath.Join(dir, "pingrb"), nil
}
func readConfig() (string, error) {
path, err := configPath()
if err != nil {
return "", err
}
data, err := os.ReadFile(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return "", errors.New("not configured. Run `pingrb configure <token>`.")
}
return "", err
}
token := strings.TrimSpace(string(data))
if token == "" {
return "", errors.New("config is empty. Run `pingrb configure <token>`.")
}
if strings.ContainsAny(token, "/ \t") {
return "", errors.New("config looks like a URL (pre-0.2.0 format). Re-run `pingrb configure <token>` with just the token.")
}
return token, nil
}
func writeConfig(input string) error {
token := strings.TrimSpace(input)
if token == "" {
return errors.New("token cannot be empty")
}
if strings.ContainsAny(token, "/ \t") {
return errors.New("expected a token, not a URL or path. Copy the token from your CLI source on pingrb.com.")
}
path, err := configPath()
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
return os.WriteFile(path, []byte(token+"\n"), 0o600)
}
func host() string {
if v := strings.TrimSpace(os.Getenv("PINGRB_HOST")); v != "" {
return strings.TrimRight(v, "/")
}
return defaultHost
}
type pingPayload struct {
Title string `json:"title"`
Body string `json:"body,omitempty"`
URL string `json:"url,omitempty"`
}
func sendPing(token, title, body, url string) error {
data, err := json.Marshal(pingPayload{Title: title, Body: body, URL: url})
if err != nil {
return err
}
endpoint := host() + "/webhooks/cli/" + token
resp, err := http.Post(endpoint, "application/json", bytes.NewReader(data))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return nil
}
respBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("pingrb returned %s: %s", resp.Status, strings.TrimSpace(string(respBody)))
}