snoobert/user.go
Vartan Benohanian 5f56273e2e User service done
Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
2020-06-18 21:41:17 -04:00

570 lines
15 KiB
Go

package geddit
import (
"context"
"encoding/json"
"errors"
"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 ...SearchOptionSetter) (*Links, *Comments, *Response, error)
OverviewOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Links, *Comments, *Response, error)
Posts(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error)
PostsOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Links, *Response, error)
Comments(ctx context.Context, opts ...SearchOptionSetter) (*Comments, *Response, error)
CommentsOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Comments, *Response, error)
Saved(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Comments, *Response, error)
Upvoted(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error)
Downvoted(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error)
Hidden(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error)
Gilded(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error)
GetFriendship(ctx context.Context, username string) (*Friendship, *Response, error)
Friend(ctx context.Context, username string) (*Friendship, *Response, error)
Unfriend(ctx context.Context, username string) (*Response, error)
Block(ctx context.Context, username string) (*Blocked, *Response, error)
// BlockByID(ctx context.Context, id string) (*Blocked, *Response, error)
Unblock(ctx context.Context, username string) (*Response, error)
// UnblockByID(ctx context.Context, id string) (*Response, error)
GetTrophies(ctx context.Context) (Trophies, *Response, error)
GetTrophiesOf(ctx context.Context, username string) (Trophies, *Response, error)
}
// UserServiceOp implements the UserService interface
type UserServiceOp struct {
client *Client
}
var _ UserService = &UserServiceOp{}
// User represents a Reddit user
type User struct {
// this 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"`
IsSuspended bool `json:"is_suspended"`
}
// 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"`
}
// Friendship represents a friend relationship.
type Friendship struct {
ID string `json:"rel_id,omitempty"`
Friend string `json:"name,omitempty"`
FriendID string `json:"id,omitempty"`
Created *Timestamp `json:"date,omitempty"`
}
// Blocked represents a blocked relationship.
type Blocked struct {
Blocked string `json:"name,omitempty"`
BlockedID string `json:"id,omitempty"`
Created *Timestamp `json:"date,omitempty"`
}
type rootTrophyListing struct {
Kind string `json:"kind,omitempty"`
Trophies Trophies `json:"data"`
}
// Trophy is a Reddit award.
type Trophy struct {
Name string `json:"name"`
}
// Trophies is a list of trophies.
type Trophies []Trophy
// UnmarshalJSON implements the json.Unmarshaler interface.
func (t *Trophies) UnmarshalJSON(b []byte) error {
var data map[string]interface{}
if err := json.Unmarshal(b, &data); err != nil {
return err
}
trophies, ok := data["trophies"]
if !ok {
return errors.New("data does not contain trophies")
}
trophyList, ok := trophies.([]interface{})
if !ok {
return errors.New("unexpected type for list of trophies")
}
for _, trophyData := range trophyList {
trophyInfo, ok := trophyData.(map[string]interface{})
if !ok {
continue
}
info, ok := trophyInfo["data"]
if !ok {
continue
}
infoBytes, err := json.Marshal(info)
if err != nil {
continue
}
var trophy Trophy
err = json.Unmarshal(infoBytes, &trophy)
if err != nil {
continue
}
*t = append(*t, trophy)
}
return nil
}
// 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 ...SearchOptionSetter) (*Links, *Comments, *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 ...SearchOptionSetter) (*Links, *Comments, *Response, error) {
form := newSearchOptions(opts...)
path := fmt.Sprintf("user/%s/overview", username)
path = addQuery(path, form)
req, err := s.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, nil, err
}
root := new(rootListing)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, nil, resp, err
}
return root.getLinks(), root.getComments(), resp, nil
}
// Posts returns a list of the client's posts.
func (s *UserServiceOp) Posts(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error) {
return s.PostsOf(ctx, s.client.Username, opts...)
}
// PostsOf returns a list of the user's posts.
func (s *UserServiceOp) PostsOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Links, *Response, error) {
form := newSearchOptions(opts...)
path := fmt.Sprintf("user/%s/submitted", username)
path = addQuery(path, form)
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.getLinks(), resp, nil
}
// Comments returns a list of the client's comments.
func (s *UserServiceOp) Comments(ctx context.Context, opts ...SearchOptionSetter) (*Comments, *Response, error) {
return s.CommentsOf(ctx, s.client.Username, opts...)
}
// CommentsOf returns a list of the user's comments.
func (s *UserServiceOp) CommentsOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Comments, *Response, error) {
form := newSearchOptions(opts...)
path := fmt.Sprintf("user/%s/comments", username)
path = addQuery(path, form)
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.getComments(), resp, nil
}
// Saved returns a list of the user's saved posts and comments.
func (s *UserServiceOp) Saved(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Comments, *Response, error) {
form := newSearchOptions(opts...)
path := fmt.Sprintf("user/%s/saved", s.client.Username)
path = addQuery(path, form)
req, err := s.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, nil, err
}
root := new(rootListing)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, nil, resp, err
}
return root.getLinks(), root.getComments(), resp, nil
}
// Upvoted returns a list of the user's upvoted posts.
func (s *UserServiceOp) Upvoted(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error) {
form := newSearchOptions(opts...)
path := fmt.Sprintf("user/%s/upvoted", s.client.Username)
path = addQuery(path, form)
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.getLinks(), resp, nil
}
// Downvoted returns a list of the user's downvoted posts.
func (s *UserServiceOp) Downvoted(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error) {
form := newSearchOptions(opts...)
path := fmt.Sprintf("user/%s/downvoted", s.client.Username)
path = addQuery(path, form)
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.getLinks(), resp, nil
}
// Hidden returns a list of the user's hidden posts.
func (s *UserServiceOp) Hidden(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error) {
form := newSearchOptions(opts...)
path := fmt.Sprintf("user/%s/hidden", s.client.Username)
path = addQuery(path, form)
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.getLinks(), resp, nil
}
// Gilded returns a list of the user's gilded posts.
func (s *UserServiceOp) Gilded(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error) {
form := newSearchOptions(opts...)
path := fmt.Sprintf("user/%s/gilded", s.client.Username)
path = addQuery(path, form)
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.getLinks(), resp, nil
}
// GetFriendship returns friendship details with the specified user.
// If the user is not your friend, it will return an error.
func (s *UserServiceOp) GetFriendship(ctx context.Context, username string) (*Friendship, *Response, error) {
path := fmt.Sprintf("api/v1/me/friends/%s", username)
req, err := s.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(Friendship)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root, resp, nil
}
// Friend friends a user.
func (s *UserServiceOp) Friend(ctx context.Context, username string) (*Friendship, *Response, error) {
type request struct {
Username string `json:"name"`
}
path := fmt.Sprintf("api/v1/me/friends/%s", username)
body := request{username}
req, err := s.client.NewRequest(http.MethodPut, path, body)
if err != nil {
return nil, nil, err
}
root := new(Friendship)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root, resp, 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)
}
// Block blocks a user.
func (s *UserServiceOp) Block(ctx context.Context, username string) (*Blocked, *Response, error) {
path := "api/block_user"
form := url.Values{}
form.Set("name", username)
req, err := s.client.NewPostForm(path, form)
if err != nil {
return nil, nil, err
}
root := new(Blocked)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root, resp, nil
}
// // BlockByID blocks a user via their full id.
// func (s *UserServiceOp) BlockByID(ctx context.Context, id string) (*Blocked, *Response, error) {
// path := "api/block_user"
// form := url.Values{}
// form.Set("account_id", id)
// req, err := s.client.NewPostForm(path, form)
// if err != nil {
// return nil, nil, err
// }
// root := new(Blocked)
// resp, err := s.client.Do(ctx, req, root)
// if err != nil {
// return nil, resp, err
// }
// return root, resp, nil
// }
// Unblock unblocks a user.
func (s *UserServiceOp) Unblock(ctx context.Context, username string) (*Response, error) {
selfID, err := s.client.GetRedditID(ctx)
if err != nil {
return nil, err
}
path := "api/unfriend"
form := url.Values{}
form.Set("name", username)
form.Set("type", "enemy")
form.Set("container", selfID)
req, err := s.client.NewPostForm(path, form)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// // UnblockByID unblocks a user via their full id.
// func (s *UserServiceOp) UnblockByID(ctx context.Context, id string) (*Response, error) {
// selfID, err := s.client.GetRedditID(ctx)
// if err != nil {
// return nil, err
// }
// path := "api/unfriend"
// form := url.Values{}
// form.Set("id", id)
// form.Set("type", "enemy")
// form.Set("container", selfID)
// req, err := s.client.NewPostForm(path, form)
// if err != nil {
// return nil, err
// }
// return s.client.Do(ctx, req, nil)
// }
// GetTrophies returns a list of your trophies.
func (s *UserServiceOp) GetTrophies(ctx context.Context) (Trophies, *Response, error) {
return s.GetTrophiesOf(ctx, s.client.Username)
}
// GetTrophiesOf returns a list of the specified user's trophies.
func (s *UserServiceOp) GetTrophiesOf(ctx context.Context, username string) (Trophies, *Response, error) {
path := fmt.Sprintf("api/v1/user/%s/trophies", username)
req, err := s.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(rootTrophyListing)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Trophies, resp, nil
}