ScuttleBot

scuttlebot / internal / ergo / api.go
Blame History Raw 180 lines
1
package ergo
2
3
import (
4
"bytes"
5
"encoding/json"
6
"fmt"
7
"net/http"
8
"time"
9
)
10
11
// APIClient is an HTTP client for Ergo's management API.
12
type APIClient struct {
13
baseURL string
14
token string
15
http *http.Client
16
}
17
18
// NewAPIClient returns a new APIClient pointed at addr with the given bearer token.
19
func NewAPIClient(addr, token string) *APIClient {
20
return &APIClient{
21
baseURL: "http://" + addr,
22
token: token,
23
http: &http.Client{Timeout: 10 * time.Second},
24
}
25
}
26
27
// Status returns the Ergo server status.
28
func (c *APIClient) Status() (*StatusResponse, error) {
29
var resp StatusResponse
30
if err := c.post("/v1/status", nil, &resp); err != nil {
31
return nil, fmt.Errorf("ergo api: status: %w", err)
32
}
33
return &resp, nil
34
}
35
36
// Rehash reloads Ergo's configuration file.
37
func (c *APIClient) Rehash() error {
38
var resp successResponse
39
if err := c.post("/v1/rehash", nil, &resp); err != nil {
40
return fmt.Errorf("ergo api: rehash: %w", err)
41
}
42
if !resp.Success {
43
return fmt.Errorf("ergo api: rehash failed: %s", resp.Error)
44
}
45
return nil
46
}
47
48
// RegisterAccount creates a NickServ account via saregister.
49
func (c *APIClient) RegisterAccount(name, passphrase string) error {
50
var resp registerResponse
51
if err := c.post("/v1/ns/saregister", map[string]string{
52
"accountName": name,
53
"passphrase": passphrase,
54
}, &resp); err != nil {
55
return fmt.Errorf("ergo api: register account %q: %w", name, err)
56
}
57
if !resp.Success {
58
return fmt.Errorf("ergo api: register account %q: %s", name, resp.ErrorCode)
59
}
60
return nil
61
}
62
63
// ChangePassword updates the passphrase of an existing NickServ account.
64
func (c *APIClient) ChangePassword(name, passphrase string) error {
65
var resp passwdResponse
66
if err := c.post("/v1/ns/passwd", map[string]string{
67
"accountName": name,
68
"passphrase": passphrase,
69
}, &resp); err != nil {
70
return fmt.Errorf("ergo api: change password %q: %w", name, err)
71
}
72
if !resp.Success {
73
return fmt.Errorf("ergo api: change password %q: %s", name, resp.ErrorCode)
74
}
75
return nil
76
}
77
78
// AccountInfo fetches details about a NickServ account.
79
func (c *APIClient) AccountInfo(name string) (*AccountInfoResponse, error) {
80
var resp AccountInfoResponse
81
if err := c.post("/v1/ns/info", map[string]string{
82
"accountName": name,
83
}, &resp); err != nil {
84
return nil, fmt.Errorf("ergo api: account info %q: %w", name, err)
85
}
86
if !resp.Success {
87
return nil, fmt.Errorf("ergo api: account %q not found", name)
88
}
89
return &resp, nil
90
}
91
92
// ListChannels returns all channels currently active on the server.
93
func (c *APIClient) ListChannels() (*ListChannelsResponse, error) {
94
var resp ListChannelsResponse
95
if err := c.post("/v1/list", nil, &resp); err != nil {
96
return nil, fmt.Errorf("ergo api: list channels: %w", err)
97
}
98
return &resp, nil
99
}
100
101
func (c *APIClient) post(path string, body any, out any) error {
102
var reqBody bytes.Buffer
103
if body != nil {
104
if err := json.NewEncoder(&reqBody).Encode(body); err != nil {
105
return err
106
}
107
}
108
109
req, err := http.NewRequest(http.MethodPost, c.baseURL+path, &reqBody)
110
if err != nil {
111
return err
112
}
113
req.Header.Set("Authorization", "Bearer "+c.token)
114
req.Header.Set("Content-Type", "application/json")
115
116
resp, err := c.http.Do(req)
117
if err != nil {
118
return err
119
}
120
defer resp.Body.Close()
121
122
if resp.StatusCode != http.StatusOK {
123
return fmt.Errorf("HTTP %d", resp.StatusCode)
124
}
125
return json.NewDecoder(resp.Body).Decode(out)
126
}
127
128
// Response types.
129
130
type successResponse struct {
131
Success bool `json:"success"`
132
Error string `json:"error,omitempty"`
133
}
134
135
type registerResponse struct {
136
Success bool `json:"success"`
137
ErrorCode string `json:"errorCode,omitempty"`
138
Error string `json:"error,omitempty"`
139
}
140
141
type passwdResponse struct {
142
Success bool `json:"success"`
143
ErrorCode string `json:"errorCode,omitempty"`
144
}
145
146
// StatusResponse is the response from /v1/status.
147
type StatusResponse struct {
148
Success bool `json:"success"`
149
Version string `json:"version"`
150
StartTime string `json:"start_time"`
151
Users struct {
152
Total int `json:"total"`
153
Max int `json:"max"`
154
} `json:"users"`
155
Channels int `json:"channels"`
156
}
157
158
// AccountInfoResponse is the response from /v1/ns/info.
159
type AccountInfoResponse struct {
160
Success bool `json:"success"`
161
AccountName string `json:"accountName"`
162
Email string `json:"email"`
163
RegisteredAt string `json:"registeredAt"`
164
Channels []string `json:"channels"`
165
}
166
167
// ChannelInfo is a single channel entry from /v1/list.
168
type ChannelInfo struct {
169
Name string `json:"name"`
170
UserCount int `json:"userCount"`
171
Topic string `json:"topic"`
172
Registered bool `json:"registered"`
173
}
174
175
// ListChannelsResponse is the response from /v1/list.
176
type ListChannelsResponse struct {
177
Success bool `json:"success"`
178
Channels []ChannelInfo `json:"channels"`
179
}
180

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button