Add methods to ModerationService, attribute to Post, use go v1.15

Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
This commit is contained in:
Vartan Benohanian 2020-09-07 21:24:14 -04:00
parent 24a87a260b
commit 078b172e81
11 changed files with 245 additions and 118 deletions

2
go.mod
View file

@ -1,6 +1,6 @@
module github.com/vartanbeno/go-reddit module github.com/vartanbeno/go-reddit
go 1.14 go 1.15
require ( require (
github.com/google/go-querystring v1.0.0 github.com/google/go-querystring v1.0.0

View file

@ -3,7 +3,6 @@ package reddit
import ( import (
"context" "context"
"fmt" "fmt"
"net/http"
"strings" "strings"
) )
@ -17,38 +16,25 @@ type ListingsService struct {
// Get posts, comments, and subreddits from their full IDs. // Get posts, comments, and subreddits from their full IDs.
func (s *ListingsService) Get(ctx context.Context, ids ...string) ([]*Post, []*Comment, []*Subreddit, *Response, error) { func (s *ListingsService) Get(ctx context.Context, ids ...string) ([]*Post, []*Comment, []*Subreddit, *Response, error) {
path := fmt.Sprintf("api/info?id=%s", strings.Join(ids, ",")) path := "api/info"
params := struct {
IDs []string `url:"id,omitempty,comma"`
}{ids}
req, err := s.client.NewRequest(http.MethodGet, path, nil) l, resp, err := s.client.getListing(ctx, path, params)
if err != nil {
return nil, nil, nil, nil, err
}
root := new(thing)
resp, err := s.client.Do(ctx, req, root)
if err != nil { if err != nil {
return nil, nil, nil, resp, err return nil, nil, nil, resp, err
} }
listing, _ := root.Listing() return l.Posts(), l.Comments(), l.Subreddits(), resp, nil
return listing.Posts(), listing.Comments(), listing.Subreddits(), resp, nil
} }
// GetPosts returns posts from their full IDs. // GetPosts returns posts from their full IDs.
func (s *ListingsService) GetPosts(ctx context.Context, ids ...string) ([]*Post, *Response, error) { func (s *ListingsService) GetPosts(ctx context.Context, ids ...string) ([]*Post, *Response, error) {
path := fmt.Sprintf("by_id/%s", strings.Join(ids, ",")) path := fmt.Sprintf("by_id/%s", strings.Join(ids, ","))
l, resp, err := s.client.getListing(ctx, path, nil)
req, err := s.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(thing)
resp, err := s.client.Do(ctx, req, root)
if err != nil { if err != nil {
return nil, resp, err return nil, resp, err
} }
return l.Posts(), resp, nil
listing, _ := root.Listing()
return listing.Posts(), resp, nil
} }

View file

@ -31,6 +31,7 @@ var expectedListingPosts = []*Post{
SubredditName: "test", SubredditName: "test",
SubredditNamePrefixed: "r/test", SubredditNamePrefixed: "r/test",
SubredditID: "t5_2qh23", SubredditID: "t5_2qh23",
SubredditSubscribers: 8202,
Author: "v_95", Author: "v_95",
AuthorID: "t2_164ab8", AuthorID: "t2_164ab8",
@ -106,6 +107,7 @@ var expectedListingPosts2 = []*Post{
SubredditName: "test", SubredditName: "test",
SubredditNamePrefixed: "r/test", SubredditNamePrefixed: "r/test",
SubredditID: "t5_2qh23", SubredditID: "t5_2qh23",
SubredditSubscribers: 8201,
Author: "v_95", Author: "v_95",
AuthorID: "t2_164ab8", AuthorID: "t2_164ab8",
@ -131,6 +133,7 @@ var expectedListingPosts2 = []*Post{
SubredditName: "test", SubredditName: "test",
SubredditNamePrefixed: "r/test", SubredditNamePrefixed: "r/test",
SubredditID: "t5_2qh23", SubredditID: "t5_2qh23",
SubredditSubscribers: 8201,
Author: "v_95", Author: "v_95",
AuthorID: "t2_164ab8", AuthorID: "t2_164ab8",

View file

@ -42,32 +42,71 @@ type ModAction struct {
SubredditID string `json:"sr_id36,omitempty"` SubredditID string `json:"sr_id36,omitempty"`
} }
// ModPermissions are the different permissions moderators have or don't have on a subreddit.
// Read about them here: https://mods.reddithelp.com/hc/en-us/articles/360009381491-User-Management-moderators-and-permissions
type ModPermissions struct {
All bool `permission:"all"`
Access bool `permission:"access"`
ChatConfig bool `permission:"chat_config"`
ChatOperator bool `permission:"chat_operator"`
Config bool `permission:"config"`
Flair bool `permission:"flair"`
Mail bool `permission:"mail"`
Posts bool `permission:"posts"`
Wiki bool `permission:"wiki"`
}
func (p *ModPermissions) String() (s string) {
if p == nil {
return "+all"
}
t := reflect.TypeOf(*p)
v := reflect.ValueOf(*p)
for i := 0; i < t.NumField(); i++ {
if v.Field(i).Kind() != reflect.Bool {
continue
}
permission := t.Field(i).Tag.Get("permission")
permitted := v.Field(i).Bool()
if permitted {
s += "+"
} else {
s += "-"
}
s += permission
if i != t.NumField()-1 {
s += ","
}
}
return
}
// BanConfig configures the ban of the user being banned.
type BanConfig struct {
Reason string `url:"reason,omitempty"`
// Not visible to the user being banned.
ModNote string `url:"note,omitempty"`
// How long the ban will last. 0-999. Leave nil for permanent.
Days *int `url:"duration,omitempty"`
// Note to include in the ban message to the user.
Message string `url:"ban_message,omitempty"`
}
// Actions gets a list of moderator actions on a subreddit. // Actions gets a list of moderator actions on a subreddit.
func (s *ModerationService) Actions(ctx context.Context, subreddit string, opts *ListModActionOptions) ([]*ModAction, *Response, error) { func (s *ModerationService) Actions(ctx context.Context, subreddit string, opts *ListModActionOptions) ([]*ModAction, *Response, error) {
path := fmt.Sprintf("r/%s/about/log", subreddit) path := fmt.Sprintf("r/%s/about/log", subreddit)
path, err := addOptions(path, opts) l, resp, err := s.client.getListing(ctx, path, opts)
if err != nil {
return nil, nil, err
}
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(thing)
resp, err := s.client.Do(ctx, req, root)
if err != nil { if err != nil {
return nil, resp, err return nil, resp, err
} }
return l.ModActions(), resp, nil
listing, _ := root.Listing()
return listing.ModActions(), resp, nil
} }
// AcceptInvite accepts a pending invite to moderate the specified subreddit. // AcceptInvite accepts a pending invite to moderate the specified subreddit.
@ -162,28 +201,55 @@ func (s *ModerationService) LeaveContributor(ctx context.Context, subredditID st
return s.client.Do(ctx, req, nil) return s.client.Do(ctx, req, nil)
} }
// Reported returns posts and comments that have been reported.
func (s *ModerationService) Reported(ctx context.Context, subreddit string, opts *ListOptions) ([]*Post, []*Comment, *Response, error) {
path := fmt.Sprintf("r/%s/about/reports", subreddit)
l, resp, err := s.client.getListing(ctx, path, opts)
if err != nil {
return nil, nil, resp, err
}
return l.Posts(), l.Comments(), resp, nil
}
// Spam returns posts and comments marked as spam.
func (s *ModerationService) Spam(ctx context.Context, subreddit string, opts *ListOptions) ([]*Post, []*Comment, *Response, error) {
path := fmt.Sprintf("r/%s/about/spam", subreddit)
l, resp, err := s.client.getListing(ctx, path, opts)
if err != nil {
return nil, nil, resp, err
}
return l.Posts(), l.Comments(), resp, nil
}
// Queue returns posts and comments requiring moderator reviews, such as one that have been
// reported or caught in the spam filter.
func (s *ModerationService) Queue(ctx context.Context, subreddit string, opts *ListOptions) ([]*Post, []*Comment, *Response, error) {
path := fmt.Sprintf("r/%s/about/modqueue", subreddit)
l, resp, err := s.client.getListing(ctx, path, opts)
if err != nil {
return nil, nil, resp, err
}
return l.Posts(), l.Comments(), resp, nil
}
// Unmoderated returns posts that have yet to be approved/removed by a mod.
func (s *ModerationService) Unmoderated(ctx context.Context, subreddit string, opts *ListOptions) ([]*Post, *Response, error) {
path := fmt.Sprintf("r/%s/about/unmoderated", subreddit)
l, resp, err := s.client.getListing(ctx, path, opts)
if err != nil {
return nil, resp, err
}
return l.Posts(), resp, nil
}
// Edited gets posts and comments that have been edited recently. // Edited gets posts and comments that have been edited recently.
func (s *ModerationService) Edited(ctx context.Context, subreddit string, opts *ListOptions) ([]*Post, []*Comment, *Response, error) { func (s *ModerationService) Edited(ctx context.Context, subreddit string, opts *ListOptions) ([]*Post, []*Comment, *Response, error) {
path := fmt.Sprintf("r/%s/about/edited", subreddit) path := fmt.Sprintf("r/%s/about/edited", subreddit)
l, resp, err := s.client.getListing(ctx, path, opts)
path, err := addOptions(path, opts)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, resp, err
} }
return l.Posts(), l.Comments(), resp, nil
req, err := s.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, nil, err
}
root := new(thing)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, nil, nil, err
}
listing, _ := root.Listing()
return listing.Posts(), listing.Comments(), resp, nil
} }
// IgnoreReports prevents reports on a post or comment from causing notifications. // IgnoreReports prevents reports on a post or comment from causing notifications.
@ -216,52 +282,6 @@ func (s *ModerationService) UnignoreReports(ctx context.Context, id string) (*Re
return s.client.Do(ctx, req, nil) return s.client.Do(ctx, req, nil)
} }
// ModPermissions are the different permissions moderators have or don't have on a subreddit.
// Read about them here: https://mods.reddithelp.com/hc/en-us/articles/360009381491-User-Management-moderators-and-permissions
type ModPermissions struct {
All bool `permission:"all"`
Access bool `permission:"access"`
ChatConfig bool `permission:"chat_config"`
ChatOperator bool `permission:"chat_operator"`
Config bool `permission:"config"`
Flair bool `permission:"flair"`
Mail bool `permission:"mail"`
Posts bool `permission:"posts"`
Wiki bool `permission:"wiki"`
}
func (p *ModPermissions) String() (s string) {
if p == nil {
return "+all"
}
t := reflect.TypeOf(*p)
v := reflect.ValueOf(*p)
for i := 0; i < t.NumField(); i++ {
if v.Field(i).Kind() != reflect.Bool {
continue
}
permission := t.Field(i).Tag.Get("permission")
permitted := v.Field(i).Bool()
if permitted {
s += "+"
} else {
s += "-"
}
s += permission
if i != t.NumField()-1 {
s += ","
}
}
return
}
// Invite a user to become a moderator of the subreddit. // Invite a user to become a moderator of the subreddit.
// If permissions is nil, all permissions will be granted. // If permissions is nil, all permissions will be granted.
func (s *ModerationService) Invite(ctx context.Context, subreddit string, username string, permissions *ModPermissions) (*Response, error) { func (s *ModerationService) Invite(ctx context.Context, subreddit string, username string, permissions *ModPermissions) (*Response, error) {
@ -305,17 +325,6 @@ func (s *ModerationService) SetPermissions(ctx context.Context, subreddit string
return s.client.Do(ctx, req, nil) return s.client.Do(ctx, req, nil)
} }
// BanConfig configures the ban of the user being banned.
type BanConfig struct {
Reason string `url:"reason,omitempty"`
// Not visible to the user being banned.
ModNote string `url:"note,omitempty"`
// How long the ban will last. 0-999. Leave nil for permanent.
Days *int `url:"duration,omitempty"`
// Note to include in the ban message to the user.
Message string `url:"ban_message,omitempty"`
}
// Ban a user from the subreddit. // Ban a user from the subreddit.
func (s *ModerationService) Ban(ctx context.Context, subreddit string, username string, config *BanConfig) (*Response, error) { func (s *ModerationService) Ban(ctx context.Context, subreddit string, username string, config *BanConfig) (*Response, error) {
path := fmt.Sprintf("r/%s/api/friend", subreddit) path := fmt.Sprintf("r/%s/api/friend", subreddit)

View file

@ -193,6 +193,102 @@ func TestModerationService_LeaveContributor(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
} }
func TestModerationService_Reported(t *testing.T) {
client, mux, teardown := setup()
defer teardown()
// contains posts and comments
blob, err := readFileContents("../testdata/user/overview.json")
require.NoError(t, err)
mux.HandleFunc("/r/testsubreddit/about/reports", func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodGet, r.Method)
fmt.Fprint(w, blob)
})
posts, comments, resp, err := client.Moderation.Reported(ctx, "testsubreddit", nil)
require.NoError(t, err)
require.Len(t, posts, 1)
require.Equal(t, expectedPost, posts[0])
require.Equal(t, "t1_f0zsa37", resp.After)
require.Len(t, comments, 1)
require.Equal(t, expectedComment, comments[0])
require.Equal(t, "t1_f0zsa37", resp.After)
}
func TestModerationService_Spam(t *testing.T) {
client, mux, teardown := setup()
defer teardown()
// contains posts and comments
blob, err := readFileContents("../testdata/user/overview.json")
require.NoError(t, err)
mux.HandleFunc("/r/testsubreddit/about/spam", func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodGet, r.Method)
fmt.Fprint(w, blob)
})
posts, comments, resp, err := client.Moderation.Spam(ctx, "testsubreddit", nil)
require.NoError(t, err)
require.Len(t, posts, 1)
require.Equal(t, expectedPost, posts[0])
require.Equal(t, "t1_f0zsa37", resp.After)
require.Len(t, comments, 1)
require.Equal(t, expectedComment, comments[0])
require.Equal(t, "t1_f0zsa37", resp.After)
}
func TestModerationService_Queue(t *testing.T) {
client, mux, teardown := setup()
defer teardown()
// contains posts and comments
blob, err := readFileContents("../testdata/user/overview.json")
require.NoError(t, err)
mux.HandleFunc("/r/testsubreddit/about/modqueue", func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodGet, r.Method)
fmt.Fprint(w, blob)
})
posts, comments, resp, err := client.Moderation.Queue(ctx, "testsubreddit", nil)
require.NoError(t, err)
require.Len(t, posts, 1)
require.Equal(t, expectedPost, posts[0])
require.Equal(t, "t1_f0zsa37", resp.After)
require.Len(t, comments, 1)
require.Equal(t, expectedComment, comments[0])
require.Equal(t, "t1_f0zsa37", resp.After)
}
func TestModerationService_Unmoderated(t *testing.T) {
client, mux, teardown := setup()
defer teardown()
// contains posts and comments
blob, err := readFileContents("../testdata/user/overview.json")
require.NoError(t, err)
mux.HandleFunc("/r/testsubreddit/about/unmoderated", func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodGet, r.Method)
fmt.Fprint(w, blob)
})
posts, resp, err := client.Moderation.Unmoderated(ctx, "testsubreddit", nil)
require.NoError(t, err)
require.Len(t, posts, 1)
require.Equal(t, expectedPost, posts[0])
require.Equal(t, "t1_f0zsa37", resp.After)
}
func TestModerationService_Edited(t *testing.T) { func TestModerationService_Edited(t *testing.T) {
client, mux, teardown := setup() client, mux, teardown := setup()
defer teardown() defer teardown()

View file

@ -30,6 +30,7 @@ var expectedPostAndComments = &PostAndComments{
SubredditName: "test", SubredditName: "test",
SubredditNamePrefixed: "r/test", SubredditNamePrefixed: "r/test",
SubredditID: "t5_2qh23", SubredditID: "t5_2qh23",
SubredditSubscribers: 8077,
Author: "testuser", Author: "testuser",
AuthorID: "t2_testuser", AuthorID: "t2_testuser",
@ -122,6 +123,7 @@ var expectedEditedPost = &Post{
SubredditName: "test", SubredditName: "test",
SubredditNamePrefixed: "r/test", SubredditNamePrefixed: "r/test",
SubredditID: "t5_2qh23", SubredditID: "t5_2qh23",
SubredditSubscribers: 8128,
Author: "v_95", Author: "v_95",
AuthorID: "t2_164ab8", AuthorID: "t2_164ab8",
@ -150,6 +152,7 @@ var expectedPost2 = &Post{
SubredditName: "test", SubredditName: "test",
SubredditNamePrefixed: "r/test", SubredditNamePrefixed: "r/test",
SubredditID: "t5_2qh23", SubredditID: "t5_2qh23",
SubredditSubscribers: 8278,
Author: "v_95", Author: "v_95",
AuthorID: "t2_164ab8", AuthorID: "t2_164ab8",
@ -176,6 +179,7 @@ var expectedPostDuplicates = []*Post{
SubredditName: "test", SubredditName: "test",
SubredditNamePrefixed: "r/test", SubredditNamePrefixed: "r/test",
SubredditID: "t5_2qh23", SubredditID: "t5_2qh23",
SubredditSubscribers: 8278,
Author: "GarlicoinAccount", Author: "GarlicoinAccount",
AuthorID: "t2_d2v1r90", AuthorID: "t2_d2v1r90",
@ -200,6 +204,7 @@ var expectedPostDuplicates = []*Post{
SubredditName: "test", SubredditName: "test",
SubredditNamePrefixed: "r/test", SubredditNamePrefixed: "r/test",
SubredditID: "t5_2qh23", SubredditID: "t5_2qh23",
SubredditSubscribers: 8278,
Author: "prog101", Author: "prog101",
AuthorID: "t2_8dyo", AuthorID: "t2_8dyo",

View file

@ -391,6 +391,27 @@ func CheckResponse(r *http.Response) error {
return errorResponse return errorResponse
} }
func (c *Client) getListing(ctx context.Context, path string, opts interface{}) (*listing, *Response, error) {
path, err := addOptions(path, opts)
if err != nil {
return nil, nil, err
}
req, err := c.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(thing)
resp, err := c.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
listing, _ := root.Listing()
return listing, resp, nil
}
// ListOptions specifies the optional parameters to various API calls that return a listing. // ListOptions specifies the optional parameters to various API calls that return a listing.
type ListOptions struct { type ListOptions struct {
// Maximum number of items to be returned. // Maximum number of items to be returned.

View file

@ -30,6 +30,7 @@ var expectedPosts = []*Post{
SubredditName: "test", SubredditName: "test",
SubredditNamePrefixed: "r/test", SubredditNamePrefixed: "r/test",
SubredditID: "t5_2qh23", SubredditID: "t5_2qh23",
SubredditSubscribers: 8154,
Author: "kmiller0112", Author: "kmiller0112",
AuthorID: "t2_30a5ktgt", AuthorID: "t2_30a5ktgt",
@ -55,6 +56,7 @@ var expectedPosts = []*Post{
SubredditName: "test", SubredditName: "test",
SubredditNamePrefixed: "r/test", SubredditNamePrefixed: "r/test",
SubredditID: "t5_2qh23", SubredditID: "t5_2qh23",
SubredditSubscribers: 8154,
Author: "MuckleMcDuckle", Author: "MuckleMcDuckle",
AuthorID: "t2_6fqntbwq", AuthorID: "t2_6fqntbwq",
@ -165,6 +167,7 @@ var expectedSearchPosts = []*Post{
SubredditName: "WatchPeopleDieInside", SubredditName: "WatchPeopleDieInside",
SubredditNamePrefixed: "r/WatchPeopleDieInside", SubredditNamePrefixed: "r/WatchPeopleDieInside",
SubredditID: "t5_3h4zq", SubredditID: "t5_3h4zq",
SubredditSubscribers: 2599948,
Author: "chocolat_ice_cream", Author: "chocolat_ice_cream",
AuthorID: "t2_3p32m02", AuthorID: "t2_3p32m02",
@ -187,6 +190,7 @@ var expectedSearchPosts = []*Post{
SubredditName: "worldnews", SubredditName: "worldnews",
SubredditNamePrefixed: "r/worldnews", SubredditNamePrefixed: "r/worldnews",
SubredditID: "t5_2qh13", SubredditID: "t5_2qh13",
SubredditSubscribers: 24651441,
Author: "Jeremy_Martin", Author: "Jeremy_Martin",
AuthorID: "t2_wgrkg", AuthorID: "t2_wgrkg",

View file

@ -486,7 +486,7 @@ type Post struct {
Title string `json:"title,omitempty"` Title string `json:"title,omitempty"`
Body string `json:"selftext,omitempty"` Body string `json:"selftext,omitempty"`
// Indicates if you've upvote/downvoted (true/false). // Indicates if you've upvoted/downvoted (true/false).
// If neither, it will be nil. // If neither, it will be nil.
Likes *bool `json:"likes"` Likes *bool `json:"likes"`
@ -497,6 +497,7 @@ type Post struct {
SubredditName string `json:"subreddit,omitempty"` SubredditName string `json:"subreddit,omitempty"`
SubredditNamePrefixed string `json:"subreddit_name_prefixed,omitempty"` SubredditNamePrefixed string `json:"subreddit_name_prefixed,omitempty"`
SubredditID string `json:"subreddit_id,omitempty"` SubredditID string `json:"subreddit_id,omitempty"`
SubredditSubscribers int `json:"subreddit_subscribers"`
Author string `json:"author,omitempty"` Author string `json:"author,omitempty"`
AuthorID string `json:"author_fullname,omitempty"` AuthorID string `json:"author_fullname,omitempty"`

View file

@ -67,6 +67,7 @@ var expectedPost = &Post{
SubredditName: "redditdev", SubredditName: "redditdev",
SubredditNamePrefixed: "r/redditdev", SubredditNamePrefixed: "r/redditdev",
SubredditID: "t5_2qizd", SubredditID: "t5_2qizd",
SubredditSubscribers: 37829,
Author: "v_95", Author: "v_95",
AuthorID: "t2_164ab8", AuthorID: "t2_164ab8",

View file

@ -69,6 +69,7 @@ var expectedWikiPageDiscussions = []*Post{
SubredditName: "helloworldtestt", SubredditName: "helloworldtestt",
SubredditNamePrefixed: "r/helloworldtestt", SubredditNamePrefixed: "r/helloworldtestt",
SubredditID: "t5_2uquw1", SubredditID: "t5_2uquw1",
SubredditSubscribers: 2,
Author: "v_95", Author: "v_95",
AuthorID: "t2_164ab8", AuthorID: "t2_164ab8",