157 lines
3.1 KiB
Go
157 lines
3.1 KiB
Go
package reddit
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
// scheme is a map of TLS=[true|false] to the scheme for that setting.
|
|
scheme = map[bool]string{
|
|
true: "https",
|
|
false: "http",
|
|
}
|
|
formEncoding = map[string][]string{
|
|
"content-type": {"application/x-www-form-urlencoded"},
|
|
}
|
|
)
|
|
|
|
type reaperConfig struct {
|
|
client client
|
|
parser parser
|
|
hostname string
|
|
reapSuffix string
|
|
tls bool
|
|
rate time.Duration
|
|
}
|
|
|
|
// reaper is a high level api for Reddit HTTP requests.
|
|
type reaper interface {
|
|
// reap executes a GET request to Reddit and returns the elements from
|
|
// the endpoint.
|
|
reap(path string, values map[string]string) (Harvest, error)
|
|
// sow executes a POST request to Reddit.
|
|
sow(path string, values map[string]string) error
|
|
// get_sow executes a POST request to Reddit
|
|
// and returns the response, usually the posted item
|
|
get_sow(path string, values map[string]string) (Submission, error)
|
|
}
|
|
|
|
type reaperImpl struct {
|
|
cli client
|
|
parser parser
|
|
hostname string
|
|
reapSuffix string
|
|
scheme string
|
|
rate time.Duration
|
|
last time.Time
|
|
mu *sync.Mutex
|
|
}
|
|
|
|
func newReaper(c reaperConfig) reaper {
|
|
return &reaperImpl{
|
|
cli: c.client,
|
|
parser: c.parser,
|
|
hostname: c.hostname,
|
|
reapSuffix: c.reapSuffix,
|
|
scheme: scheme[c.tls],
|
|
rate: c.rate,
|
|
mu: &sync.Mutex{},
|
|
}
|
|
}
|
|
|
|
func (r *reaperImpl) reap(path string, values map[string]string) (Harvest, error) {
|
|
r.rateBlock()
|
|
resp, err := r.cli.Do(
|
|
&http.Request{
|
|
Method: "GET",
|
|
URL: r.url(r.path(path, r.reapSuffix), values),
|
|
Host: r.hostname,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return Harvest{}, err
|
|
}
|
|
|
|
comments, posts, messages, mores, err := r.parser.parse(resp)
|
|
return Harvest{
|
|
Comments: comments,
|
|
Posts: posts,
|
|
Messages: messages,
|
|
Mores: mores,
|
|
}, err
|
|
}
|
|
|
|
func (r *reaperImpl) sow(path string, values map[string]string) error {
|
|
r.rateBlock()
|
|
_, err := r.cli.Do(
|
|
&http.Request{
|
|
Method: "POST",
|
|
Header: formEncoding,
|
|
Host: r.hostname,
|
|
URL: r.url(path, values),
|
|
},
|
|
)
|
|
|
|
return err
|
|
}
|
|
|
|
func (r *reaperImpl) get_sow(path string, values map[string]string) (Submission, error) {
|
|
r.rateBlock()
|
|
values["api_type"] = "json"
|
|
resp, err := r.cli.Do(
|
|
&http.Request{
|
|
Method: "POST",
|
|
Header: formEncoding,
|
|
Host: r.hostname,
|
|
URL: r.url(path, values),
|
|
},
|
|
)
|
|
|
|
if err != nil {
|
|
return Submission{}, err
|
|
}
|
|
|
|
return r.parser.parse_submitted(resp)
|
|
}
|
|
|
|
func (r *reaperImpl) rateBlock() {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
if time.Since(r.last) < r.rate {
|
|
<-time.After(r.last.Add(r.rate).Sub(time.Now()))
|
|
}
|
|
r.last = time.Now()
|
|
}
|
|
|
|
func (r *reaperImpl) url(path string, values map[string]string) *url.URL {
|
|
return &url.URL{
|
|
Scheme: r.scheme,
|
|
Host: r.hostname,
|
|
Path: path,
|
|
RawQuery: r.formatValues(values).Encode(),
|
|
}
|
|
}
|
|
|
|
func (r *reaperImpl) path(p string, suff string) string {
|
|
if strings.HasSuffix(p, suff) {
|
|
return p
|
|
}
|
|
|
|
return p + suff
|
|
}
|
|
|
|
func (r *reaperImpl) formatValues(values map[string]string) url.Values {
|
|
formattedValues := url.Values{}
|
|
|
|
for key, value := range values {
|
|
formattedValues[key] = []string{value}
|
|
}
|
|
|
|
return formattedValues
|
|
}
|