snoobert/user.go
Vartan Benohanian 2696d8e32c Method chaining for user posts/comments
Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
2020-05-17 20:17:20 -04:00

437 lines
12 KiB
Go

package geddit
import (
"context"
"fmt"
"net/http"
"net/url"
)
// UserService handles communication with the user
// related methods of the Reddit API
type UserService interface {
Get(ctx context.Context, username string) (*User, *Response, error)
GetMultipleByID(ctx context.Context, ids ...string) (map[string]*UserShort, *Response, error)
UsernameAvailable(ctx context.Context, username string) (bool, *Response, error)
Overview(ctx context.Context, opts *ListOptions) (*CommentsLinks, *Response, error)
OverviewOf(ctx context.Context, username string, opts *ListOptions) (*CommentsLinks, *Response, error)
GetPosts() *UserPostFinder
GetPostsOf(username string) *UserPostFinder
GetComments() *UserCommentFinder
GetCommentsOf(username string) *UserCommentFinder
GetUpvoted() *UserPostFinder
GetDownvoted() *UserPostFinder
GetHidden() *UserPostFinder
GetSaved(ctx context.Context, opts *ListOptions) (*CommentsLinks, *Response, error)
GetGilded(ctx context.Context, opts *ListOptions) (*CommentsLinks, *Response, error)
Friend(ctx context.Context, username string, note string) (interface{}, *Response, error)
Unblock(ctx context.Context, username string) (*Response, error)
Unfriend(ctx context.Context, username string) (*Response, error)
}
// UserServiceOp implements the UserService interface
type UserServiceOp struct {
client *Client
}
var _ UserService = &UserServiceOp{}
// User represents a Reddit user
type User struct {
// is not the full ID, watch out
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Created *Timestamp `json:"created_utc,omitempty"`
LinkKarma int `json:"link_karma"`
CommentKarma int `json:"comment_karma"`
IsFriend bool `json:"is_friend"`
IsEmployee bool `json:"is_employee"`
HasVerifiedEmail bool `json:"has_verified_email"`
NSFW bool `json:"over_18"`
}
// UserShort represents a Reddit user, but contains fewer pieces of information
// It is returned from the GET /api/user_data_by_account_ids endpoint
type UserShort struct {
Name string `json:"name,omitempty"`
Created *Timestamp `json:"created_utc,omitempty"`
LinkKarma int `json:"link_karma"`
CommentKarma int `json:"comment_karma"`
NSFW bool `json:"profile_over_18"`
}
// Get returns information about the user
func (s *UserServiceOp) Get(ctx context.Context, username string) (*User, *Response, error) {
path := fmt.Sprintf("user/%s/about", username)
req, err := s.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(userRoot)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Data, resp, nil
}
// GetMultipleByID returns multiple users from their full IDs
// The response body is a map where the keys are the IDs (if they exist), and the value is the user
func (s *UserServiceOp) GetMultipleByID(ctx context.Context, ids ...string) (map[string]*UserShort, *Response, error) {
type query struct {
IDs []string `url:"ids,omitempty,comma"`
}
path := "api/user_data_by_account_ids"
path, err := addOptions(path, query{ids})
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(map[string]*UserShort)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return *root, resp, nil
}
// UsernameAvailable checks whether a username is available for registration
// If a valid username is provided, this endpoint returns a body with just "true" or "false"
func (s *UserServiceOp) UsernameAvailable(ctx context.Context, username string) (bool, *Response, error) {
type query struct {
User string `url:"user,omitempty"`
}
path := "api/username_available"
path, err := addOptions(path, query{username})
if err != nil {
return false, nil, err
}
req, err := s.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return false, nil, err
}
root := new(bool)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return false, resp, err
}
return *root, resp, nil
}
// Overview returns a list of the client's comments and links
func (s *UserServiceOp) Overview(ctx context.Context, opts *ListOptions) (*CommentsLinks, *Response, error) {
return s.OverviewOf(ctx, s.client.Username, opts)
}
// OverviewOf returns a list of the user's comments and links
func (s *UserServiceOp) OverviewOf(ctx context.Context, username string, opts *ListOptions) (*CommentsLinks, *Response, error) {
path := fmt.Sprintf("user/%s/overview", username)
return s.getCommentsAndLinks(ctx, path, opts)
}
// GetPosts returns a list of the client's posts.
func (s *UserServiceOp) GetPosts() *UserPostFinder {
return s.GetPostsOf(s.client.Username)
}
// GetPostsOf returns a list of the user's posts.
func (s *UserServiceOp) GetPostsOf(username string) *UserPostFinder {
return newUserPostFinder(s.client, username, "submitted")
}
// GetComments returns a list of the client's comments.
func (s *UserServiceOp) GetComments() *UserCommentFinder {
return s.GetCommentsOf(s.client.Username)
}
// GetCommentsOf returns a list of a user's comments.
func (s *UserServiceOp) GetCommentsOf(username string) *UserCommentFinder {
f := new(UserCommentFinder)
f.client = s.client
f.username = username
return f
}
// GetUpvoted returns a list of the client's upvoted submissions
func (s *UserServiceOp) GetUpvoted() *UserPostFinder {
return newUserPostFinder(s.client, s.client.Username, "upvoted")
}
// GetDownvoted returns a list of the client's downvoted submissions
func (s *UserServiceOp) GetDownvoted() *UserPostFinder {
return newUserPostFinder(s.client, s.client.Username, "downvoted")
}
// GetHidden returns a list of the client's hidden submissions
func (s *UserServiceOp) GetHidden() *UserPostFinder {
return newUserPostFinder(s.client, s.client.Username, "hidden")
}
// GetSaved returns a list of the client's saved comments and links
func (s *UserServiceOp) GetSaved(ctx context.Context, opts *ListOptions) (*CommentsLinks, *Response, error) {
path := fmt.Sprintf("user/%s/saved", s.client.Username)
return s.getCommentsAndLinks(ctx, path, opts)
}
// GetGilded returns a list of the client's gilded comments and links
func (s *UserServiceOp) GetGilded(ctx context.Context, opts *ListOptions) (*CommentsLinks, *Response, error) {
path := fmt.Sprintf("user/%s/gilded", s.client.Username)
return s.getCommentsAndLinks(ctx, path, opts)
}
// Friend creates or updates a "friend" relationship
// Request body contains JSON data with:
// name: existing Reddit username
// note: a string no longer than 300 characters
func (s *UserServiceOp) Friend(ctx context.Context, username string, note string) (interface{}, *Response, error) {
type request struct {
Name string `url:"name"`
Note string `url:"note"`
}
path := fmt.Sprintf("api/v1/me/friends/%s", username)
body := request{Name: username, Note: note}
_, err := s.client.NewRequest(http.MethodPut, path, body)
if err != nil {
return false, nil, err
}
// todo: requires gold
return nil, nil, nil
}
// Unblock unblocks a user
func (s *UserServiceOp) Unblock(ctx context.Context, username string) (*Response, error) {
path := "api/unfriend"
form := url.Values{}
form.Set("name", username)
form.Set("type", "enemy")
form.Set("container", "todo: this should be the current user's full id")
req, err := s.client.NewPostForm(path, form)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// Unfriend unfriends a user
func (s *UserServiceOp) Unfriend(ctx context.Context, username string) (*Response, error) {
path := fmt.Sprintf("api/v1/me/friends/%s", username)
req, err := s.client.NewRequest(http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
func (s *UserServiceOp) getLinks(ctx context.Context, path string, opts *ListOptions) (*Links, *Response, error) {
listing, resp, err := s.getListing(ctx, path, opts)
if err != nil {
return nil, resp, err
}
return listing.getLinks(), resp, nil
}
func (s *UserServiceOp) getComments(ctx context.Context, path string, opts *ListOptions) (*Comments, *Response, error) {
listing, resp, err := s.getListing(ctx, path, opts)
if err != nil {
return nil, resp, err
}
return listing.getComments(), resp, nil
}
func (s *UserServiceOp) getCommentsAndLinks(ctx context.Context, path string, opts *ListOptions) (*CommentsLinks, *Response, error) {
listing, resp, err := s.getListing(ctx, path, opts)
if err != nil {
return nil, resp, err
}
v := new(CommentsLinks)
v.Comments = listing.getComments().Comments
v.Links = listing.getLinks().Links
v.After = listing.getAfter()
v.Before = listing.getBefore()
return v, resp, nil
}
func (s *UserServiceOp) getListing(ctx context.Context, path string, opts *ListOptions) (*rootListing, *Response, error) {
path, err := addOptions(path, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(rootListing)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root, resp, err
}
// UserPostFinder finds the posts of a user.
type UserPostFinder struct {
client *Client
username string
// where can be submitted, upvoted, downvoted, hidden
// https://www.reddit.com/dev/api/#GET_user_{username}_{where}
where string
opts struct {
After string `url:"after,omitempty"`
Before string `url:"before,omitempty"`
Limit int `url:"limit,omitempty"`
Sort string `url:"sort,omitempty"`
}
}
func newUserPostFinder(cli *Client, username string, where string) *UserPostFinder {
f := new(UserPostFinder)
f.client = cli
f.username = username
f.where = where
return f
}
// After sets the after option.
func (f *UserPostFinder) After(after string) *UserPostFinder {
f.opts.After = after
return f
}
// Before sets the before option.
func (f *UserPostFinder) Before(before string) *UserPostFinder {
f.opts.Before = before
return f
}
// Limit sets the limit option.
func (f *UserPostFinder) Limit(limit int) *UserPostFinder {
f.opts.Limit = limit
return f
}
// Sort sets the sort option.
func (f *UserPostFinder) Sort(sort Sort) *UserPostFinder {
f.opts.Sort = sort.String()
return f
}
// Do conducts the search.
func (f *UserPostFinder) Do(ctx context.Context) (*Links, *Response, error) {
path := fmt.Sprintf("user/%s/%s", f.username, f.where)
path, err := addOptions(path, f.opts)
if err != nil {
return nil, nil, err
}
req, err := f.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(rootListing)
resp, err := f.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.getLinks(), resp, nil
}
// UserCommentFinder finds the comments of a user.
type UserCommentFinder struct {
client *Client
username string
opts struct {
After string `url:"after,omitempty"`
Before string `url:"before,omitempty"`
Limit int `url:"limit,omitempty"`
Sort string `url:"sort,omitempty"`
}
}
// OfUser specified the user we want to get the comments of.
func (f *UserCommentFinder) OfUser(username string) *UserCommentFinder {
f.username = username
return f
}
// After sets the after option.
func (f *UserCommentFinder) After(after string) *UserCommentFinder {
f.opts.After = after
return f
}
// Before sets the before option.
func (f *UserCommentFinder) Before(before string) *UserCommentFinder {
f.opts.Before = before
return f
}
// Limit sets the limit option.
func (f *UserCommentFinder) Limit(limit int) *UserCommentFinder {
f.opts.Limit = limit
return f
}
// Sort sets the sort option.
func (f *UserCommentFinder) Sort(sort Sort) *UserCommentFinder {
f.opts.Sort = sort.String()
return f
}
// Do conducts the search.
func (f *UserCommentFinder) Do(ctx context.Context) (*Comments, *Response, error) {
path := fmt.Sprintf("user/%s/comments", f.username)
path, err := addOptions(path, f.opts)
if err != nil {
return nil, nil, err
}
req, err := f.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(rootListing)
resp, err := f.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.getComments(), resp, nil
}