Get subreddit by name
Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
This commit is contained in:
parent
2f4d11ce55
commit
3c7aee142d
14
README.md
14
README.md
@ -1,3 +1,17 @@
|
|||||||
# Geddit
|
# Geddit
|
||||||
|
|
||||||
Geddit is a Go client library for accessing the Reddit API.
|
Geddit is a Go client library for accessing the Reddit API.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
To get a specific version from the list of [versions](https://github.com/vartanbeno/geddit/releases):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get github.com/vartanbeno/geddit@vX.Y.Z
|
||||||
|
```
|
||||||
|
|
||||||
|
Or for the latest version:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get github.com/vartanbeno/geddit
|
||||||
|
```
|
||||||
|
200
geddit.go
Normal file
200
geddit.go
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
package geddit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
libraryVersion = "0.0.1"
|
||||||
|
defaultBaseURL = "https://reddit.com"
|
||||||
|
defaultBaseURLOauth = "https://oauth.reddit.com"
|
||||||
|
userAgent = "geddit/" + libraryVersion
|
||||||
|
mediaType = "application/json"
|
||||||
|
|
||||||
|
headerContentType = "Content-Type"
|
||||||
|
headerAccept = "Accept"
|
||||||
|
headerUserAgent = "User-Agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestCompletionCallback defines the type of the request callback function
|
||||||
|
type RequestCompletionCallback func(*http.Request, *http.Response)
|
||||||
|
|
||||||
|
// Client manages communication with the Reddit API
|
||||||
|
type Client struct {
|
||||||
|
// HTTP client used to communicate with the Reddit API
|
||||||
|
client *http.Client
|
||||||
|
|
||||||
|
// Base URL for HTTP requests
|
||||||
|
BaseURL *url.URL
|
||||||
|
|
||||||
|
UserAgent string
|
||||||
|
|
||||||
|
Subreddit SubredditService
|
||||||
|
|
||||||
|
onRequestCompleted RequestCompletionCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnRequestCompleted sets the client's request completion callback
|
||||||
|
func (c *Client) OnRequestCompleted(rc RequestCompletionCallback) {
|
||||||
|
c.onRequestCompleted = rc
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClient(httpClient *http.Client) *Client {
|
||||||
|
if httpClient == nil {
|
||||||
|
httpClient = &http.Client{}
|
||||||
|
}
|
||||||
|
|
||||||
|
baseURL, _ := url.Parse(defaultBaseURL)
|
||||||
|
|
||||||
|
c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent}
|
||||||
|
c.Subreddit = &SubredditServiceOp{client: c}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a client that can make requests to the Reddit API
|
||||||
|
func New(httpClient *http.Client, opts ...Opt) (c *Client, err error) {
|
||||||
|
c = newClient(httpClient)
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err = opt(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequest creates an API request
|
||||||
|
// The path is the relative URL which will be resolves to the BaseURL of the Client
|
||||||
|
// It should always be specified without a preceding slash
|
||||||
|
func (c *Client) NewRequest(method, path string, body interface{}) (*http.Request, error) {
|
||||||
|
u, err := c.BaseURL.Parse(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if body != nil {
|
||||||
|
err = json.NewEncoder(buf).Encode(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := bytes.NewReader(buf.Bytes())
|
||||||
|
req, err := http.NewRequest(method, u.String(), reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add(headerContentType, mediaType)
|
||||||
|
req.Header.Add(headerAccept, mediaType)
|
||||||
|
req.Header.Add(headerUserAgent, c.UserAgent)
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response is a PlayNetwork response. This wraps the standard http.Response returned from PlayNetwork.
|
||||||
|
type Response struct {
|
||||||
|
*http.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// newResponse creates a new Response for the provided http.Response
|
||||||
|
func newResponse(r *http.Response) *Response {
|
||||||
|
response := Response{Response: r}
|
||||||
|
return &response
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do sends an API request and returns the API response. The API response is JSON decoded and stored in the value
|
||||||
|
// pointed to by v, or returned as an error if an API error has occurred. If v implements the io.Writer interface,
|
||||||
|
// the raw response will be written to v, without attempting to decode it.
|
||||||
|
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
|
||||||
|
resp, err := DoRequestWithClient(ctx, c.client, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.onRequestCompleted != nil {
|
||||||
|
c.onRequestCompleted(req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := newResponse(resp)
|
||||||
|
defer func() {
|
||||||
|
if rerr := response.Body.Close(); err == nil {
|
||||||
|
err = rerr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = CheckResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return response, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v != nil {
|
||||||
|
if w, ok := v.(io.Writer); ok {
|
||||||
|
_, err = io.Copy(w, response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = json.NewDecoder(response.Body).Decode(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoRequest submits an HTTP request.
|
||||||
|
func DoRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||||
|
return DoRequestWithClient(ctx, http.DefaultClient, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoRequestWithClient submits an HTTP request using the specified client.
|
||||||
|
func DoRequestWithClient(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
return client.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An ErrorResponse reports the error caused by an API request
|
||||||
|
type ErrorResponse struct {
|
||||||
|
// HTTP response that caused this error
|
||||||
|
Response *http.Response
|
||||||
|
|
||||||
|
// Error message
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ErrorResponse) Error() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%v %v: %d %v",
|
||||||
|
r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, r.Message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckResponse checks the API response for errors, and returns them if present.
|
||||||
|
// A response is considered an error if it has a status code outside the 200 range.
|
||||||
|
func CheckResponse(r *http.Response) error {
|
||||||
|
if c := r.StatusCode; c >= 200 && c <= 299 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errorResponse := &ErrorResponse{Response: r}
|
||||||
|
data, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err == nil && len(data) > 0 {
|
||||||
|
err := json.Unmarshal(data, errorResponse)
|
||||||
|
if err != nil {
|
||||||
|
errorResponse.Message = string(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorResponse
|
||||||
|
}
|
14
geddit_opts.go
Normal file
14
geddit_opts.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package geddit
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Opt is a configuration option to initialize a client
|
||||||
|
type Opt func(*Client) error
|
||||||
|
|
||||||
|
// WithUserAgent sets the user agent for the client
|
||||||
|
func WithUserAgent(ua string) Opt {
|
||||||
|
return func(c *Client) error {
|
||||||
|
c.UserAgent = fmt.Sprintf("%s %s", ua, c.UserAgent)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
64
subreddit.go
Normal file
64
subreddit.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package geddit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SubredditService handles communication with the subreddit
|
||||||
|
// related methods of the Reddit API
|
||||||
|
type SubredditService interface {
|
||||||
|
GetByName(ctx context.Context, name string) (*Subreddit, *Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubredditServiceOp implements the SubredditService interface
|
||||||
|
type SubredditServiceOp struct {
|
||||||
|
client *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ SubredditService = &SubredditServiceOp{}
|
||||||
|
|
||||||
|
type subredditRoot struct {
|
||||||
|
Kind *string `json:"kind,omitempty"`
|
||||||
|
Data *Subreddit `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subreddit holds information about a subreddit
|
||||||
|
type Subreddit struct {
|
||||||
|
ID *string `json:"id,omitempty"`
|
||||||
|
FullID *string `json:"name,omitempty"`
|
||||||
|
Created *float64 `json:"created_utc,omitempty"`
|
||||||
|
|
||||||
|
URL *string `json:"url,omitempty"`
|
||||||
|
DisplayName *string `json:"display_name,omitempty"`
|
||||||
|
DisplayNamePrefixed *string `json:"display_name_prefixed,omitempty"`
|
||||||
|
Title *string `json:"title,omitempty"`
|
||||||
|
PublicDescription *string `json:"public_description,omitempty"`
|
||||||
|
|
||||||
|
Subscribers *int `json:"subscribers,omitempty"`
|
||||||
|
ActiveUserCount *int `json:"active_user_count,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByName gets a subreddit by name
|
||||||
|
func (s *SubredditServiceOp) GetByName(ctx context.Context, name string) (*Subreddit, *Response, error) {
|
||||||
|
if name == "" {
|
||||||
|
return nil, nil, errors.New("empty subreddit name provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("r/%s/about.json", name)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest(http.MethodGet, path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
root := new(subredditRoot)
|
||||||
|
resp, err := s.client.Do(ctx, req, root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.Data, resp, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user