/
pixelapi.go
182 lines (153 loc) · 4.64 KB
/
pixelapi.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
package pixelapi
import (
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
"time"
)
// PixelAPI is the Pixeldrain API client
type PixelAPI struct {
client *http.Client
apiEndpoint string
key string
realIP string
realAgent string
}
// New creates a new Pixeldrain API client to query the Pixeldrain API with
func New(apiEndpoint string) (api PixelAPI) {
return PixelAPI{
client: &http.Client{Timeout: time.Minute * 5},
apiEndpoint: apiEndpoint,
}
}
func (p PixelAPI) UnixSocketPath(socket string) PixelAPI {
// Pixeldrain uses unix domain sockets on its servers to minimize latency
// between the web interface daemon and API daemon. Golang does not
// understand that it needs to dial a unix socket on this case so we create
// a custom HTTP transport which uses the unix socket instead of TCP
// Fake the dialer to use a unix socket instead of TCP
p.client.Transport = &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socket)
},
}
// The hostname part of the URL is not used, but the protocol and path are.
// The pixeldrain unix socket doesn't use https so we need to disable it
p.apiEndpoint = strings.Replace(p.apiEndpoint, "https://", "http://", 1)
return p
}
// Login logs a user into the pixeldrain API. The original PixelAPI does not get
// logged in, only the returned PixelAPI
func (p PixelAPI) Login(apiKey string) PixelAPI {
p.key = apiKey
return p
}
// RealIP sets the real IP address to use when making API requests
func (p PixelAPI) RealIP(ip string) PixelAPI {
p.realIP = ip
return p
}
// RealAgent sets the real user agent to use when making API requests
func (p PixelAPI) RealAgent(agent string) PixelAPI {
p.realAgent = agent
return p
}
// Standard response types
// Error is an error returned by the pixeldrain API. If the request failed
// before it could reach the API the error will be on a different type
type Error struct {
Status int `json:"-"` // One of the http.Status types
Success bool `json:"success"`
StatusCode string `json:"value"`
Message string `json:"message"`
// In case of the multiple_errors code this array will be populated with
// more errors
Errors []Error `json:"errors,omitempty"`
// Metadata regarding the error
Extra map[string]interface{} `json:"extra,omitempty"`
}
func (e Error) Error() string { return e.StatusCode }
// ErrIsServerError returns true if the error is a server-side error
func ErrIsServerError(err error) bool {
if apierr, ok := err.(Error); ok && apierr.Status >= 500 {
return true
}
return false
}
// ErrIsClientError returns true if the error is a client-side error
func ErrIsClientError(err error) bool {
if apierr, ok := err.(Error); ok && apierr.Status >= 400 && apierr.Status < 500 {
return true
}
return false
}
func (p *PixelAPI) do(r *http.Request) (*http.Response, error) {
if p.key != "" {
r.SetBasicAuth("", p.key)
}
if p.realIP != "" {
r.Header.Set("X-Real-IP", p.realIP)
}
if p.realAgent != "" {
r.Header.Set("User-Agent", p.realAgent)
}
return p.client.Do(r)
}
func (p *PixelAPI) getRaw(path string) (io.ReadCloser, error) {
req, err := http.NewRequest("GET", p.apiEndpoint+"/"+path, nil)
if err != nil {
return nil, err
}
resp, err := p.do(req)
if err != nil {
return nil, err
}
return resp.Body, err
}
func (p *PixelAPI) jsonRequest(method, path string, target interface{}) error {
req, err := http.NewRequest(method, p.apiEndpoint+"/"+path, nil)
if err != nil {
return err
}
resp, err := p.do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return parseJSONResponse(resp, target)
}
func (p *PixelAPI) form(method, url string, vals url.Values, target interface{}) error {
req, err := http.NewRequest(method, p.apiEndpoint+"/"+url, strings.NewReader(vals.Encode()))
if err != nil {
return fmt.Errorf("prepare request failed: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := p.do(req)
if err != nil {
return fmt.Errorf("do request failed: %w", err)
}
defer resp.Body.Close()
return parseJSONResponse(resp, target)
}
func parseJSONResponse(resp *http.Response, target interface{}) (err error) {
// Test for client side and server side errors
if resp.StatusCode >= 400 {
errResp := Error{Status: resp.StatusCode}
if err = json.NewDecoder(resp.Body).Decode(&errResp); err != nil {
return fmt.Errorf("failed to decode json error: %w", err)
}
return errResp
}
if target == nil {
return nil
}
if err = json.NewDecoder(resp.Body).Decode(target); err != nil {
return fmt.Errorf("failed to decode json response: %w", err)
}
return nil
}