/
api.go
157 lines (128 loc) · 3.21 KB
/
api.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
package derpigo
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
// derpigo-specific errors.
var (
ErrNeedsOneSlash = errors.New("derpigo: this needs one slash in its invocation")
)
/*
Connection models the connection to the Derpibooru API.
*/
type Connection struct {
apiKey string // API key for all DB communication
domain string // domain name to communicate with
cli *http.Client // HTTP client for communication (default is http.DefaultClient)
}
// Option is a function that modifies the given Connection.
type Option func(*Connection)
// WithAPIKey specifies a given API key for all API calls.
func WithAPIKey(apiKey string) Option {
// automatically trim newline from end of API key.
if strings.HasSuffix(apiKey, "\n") {
apiKey = strings.Split(apiKey, "\n")[0]
}
return func(c *Connection) {
c.apiKey = apiKey
}
}
// WithDomain specifies a different base domain to do API calls against.
func WithDomain(domain string) Option {
return func(c *Connection) {
c.domain = domain
}
}
// WithClient changes the HTTP client used to talk to derpibooru.
func WithClient(cli *http.Client) Option {
return func(c *Connection) {
c.cli = cli
}
}
// New creates a new connection to the Derpibooru API.
func New(options ...Option) (c *Connection) {
c = &Connection{domain: "derpibooru.org"}
for _, opt := range options {
opt(c)
}
if c.cli == nil {
c.cli = http.DefaultClient
}
return
}
func (c *Connection) apiCall(ctx context.Context, method, route string, args url.Values, body interface{}, wantResponseCode int) ([]byte, []Interaction, error) {
var (
buf *bytes.Buffer = bytes.NewBuffer(nil)
req *http.Request
err error
)
purl, err := url.Parse(fmt.Sprintf("https://%s/%s", c.domain, route))
if err != nil {
return nil, nil, err
}
if ak := c.apiKey; ak != "" {
args.Add("key", ak)
}
purl.RawQuery = args.Encode()
urlStr := purl.String()
if body != nil {
err = json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, nil, err
}
req, err = http.NewRequest(method, urlStr, buf)
} else {
req, err = http.NewRequest(method, urlStr, nil)
}
if err != nil {
return nil, nil, err
}
req = req.WithContext(ctx)
resp, err := c.cli.Do(req)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
}
type interactionsWrapper struct {
Interactions []Interaction `json:"interactions"`
}
iw := interactionsWrapper{}
// if this fails it's okay
_ = json.Unmarshal(data, &iw)
if resp.StatusCode != wantResponseCode {
purl.RawQuery = ""
return nil, nil, NewError(
fmt.Errorf(
"derpigo: expected code %d for %s, got %d",
wantResponseCode,
purl.String(),
resp.StatusCode,
),
resp,
)
}
return data, iw.Interactions, nil
}
// Interaction is the "hard copy" of user interactions on images. Possible kinds include (but are not limited to):
//
// - down
// - up
// - faved
type Interaction struct {
ID int `json:"id"`
InteractionType string `json:"interaction_type"`
Value string `json:"value"`
UserID int `json:"user_id"`
ImageID int `json:"image_id"`
}