forked from AcalephStorage/consul-alerts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
hipchat.go
201 lines (172 loc) · 5.03 KB
/
hipchat.go
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Package hipchat provides a client for using the HipChat API v2.
package hipchat
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"mime"
"net/http"
"net/url"
"os"
"os/user"
"path/filepath"
"strings"
)
const (
defaultBaseURL = "https://api.hipchat.com/v2/"
)
// Client manages the communication with the HipChat API.
type Client struct {
authToken string
BaseURL *url.URL
client *http.Client
// Room gives access to the /room part of the API.
Room *RoomService
// User gives access to the /user part of the API.
User *UserService
// Emoticon gives access to the /emoticon part of the API.
Emoticon *EmoticonService
}
// Links represents the HipChat default links.
type Links struct {
Self string `json:"self"`
}
// PageLinks represents the HipChat page links.
type PageLinks struct {
Links
Prev string `json:"prev"`
Next string `json:"next"`
}
// ID represents a HipChat id.
// Use a separate struct because it can be a string or a int.
type ID struct {
ID string `json:"id"`
}
// NewClient returns a new HipChat API client. You must provide a valid
// AuthToken retrieved from your HipChat account.
func NewClient(authToken string) *Client {
baseURL, err := url.Parse(defaultBaseURL)
if err != nil {
panic(err)
}
c := &Client{
authToken: authToken,
BaseURL: baseURL,
client: http.DefaultClient,
}
c.Room = &RoomService{client: c}
c.User = &UserService{client: c}
c.Emoticon = &EmoticonService{client: c}
return c
}
// NewRequest creates an API request. This method can be used to performs
// API request not implemented in this library. Otherwise it should not be
// be used directly.
// Relative URLs should always be specified without a preceding slash.
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
rel, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
u := c.BaseURL.ResolveReference(rel)
buf := new(bytes.Buffer)
if body != nil {
err := json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
}
}
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}
req.Header.Add("Authorization", "Bearer "+c.authToken)
req.Header.Add("Content-Type", "application/json")
return req, nil
}
// NewFileUploadRequest creates an API request to upload a file.
// This method manually formats the request as multipart/related with a single part
// of content-type application/json and a second part containing the file to be sent.
// Relative URLs should always be specified without a preceding slash.
func (c *Client) NewFileUploadRequest(method, urlStr string, v interface{}) (*http.Request, error) {
rel, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
u := c.BaseURL.ResolveReference(rel)
shareFileReq, ok := v.(*ShareFileRequest)
if !ok {
return nil, errors.New("ShareFileRequest corrupted")
}
path := shareFileReq.Path
message := shareFileReq.Message
// Resolve home path
if strings.HasPrefix(path, "~") {
usr, _ := user.Current()
path = strings.Replace(path, "~", usr.HomeDir, 1)
}
// Check if file exists
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, err
}
// Read file and encode to base 64
file, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
b64 := base64.StdEncoding.EncodeToString(file)
contentType := mime.TypeByExtension(filepath.Ext(path))
// Set proper filename
filename := shareFileReq.Filename
if filename == "" {
filename = filepath.Base(path)
} else if filepath.Ext(filename) != filepath.Ext(path) {
filename = filepath.Base(filename) + filepath.Ext(path)
}
// Build request body
body := "--hipfileboundary\n" +
"Content-Type: application/json; charset=UTF-8\n" +
"Content-Disposition: attachment; name=\"metadata\"\n\n" +
"{\"message\": \"" + message + "\"}\n" +
"--hipfileboundary\n" +
"Content-Type: " + contentType + " charset=UTF-8\n" +
"Content-Transfer-Encoding: base64\n" +
"Content-Disposition: attachment; name=file; filename=" + filename + "\n\n" +
b64 + "\n" +
"--hipfileboundary\n"
b := &bytes.Buffer{}
b.Write([]byte(body))
req, err := http.NewRequest(method, u.String(), b)
if err != nil {
return nil, err
}
req.Header.Add("Authorization", "Bearer "+c.authToken)
req.Header.Add("Content-Type", "multipart/related; boundary=hipfileboundary")
return req, err
}
// Do performs the request, the json received in the response is decoded
// and stored in the value pointed by v.
// Do can be used to perform the request created with NewRequest, as the latter
// it should be used only for API requests not implemented in this library.
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if c := resp.StatusCode; c < 200 || c > 299 {
return resp, fmt.Errorf("Server returns status %d", c)
}
if v != nil {
if w, ok := v.(io.Writer); ok {
io.Copy(w, resp.Body)
} else {
err = json.NewDecoder(resp.Body).Decode(v)
}
}
return resp, err
}