snoobert/reddit/message.go

314 lines
7.4 KiB
Go
Raw Normal View History

package reddit
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/url"
"strings"
"github.com/google/go-querystring/query"
)
// MessageService handles communication with the message
// related methods of the Reddit API.
//
// Reddit API docs: https://www.reddit.com/dev/api/#section_messages
type MessageService struct {
client *Client
}
// Message is a message.
type Message struct {
ID string `json:"id"`
FullID string `json:"name"`
Created *Timestamp `json:"created_utc"`
Subject string `json:"subject"`
Text string `json:"body"`
ParentID string `json:"parent_id"`
Author string `json:"author"`
To string `json:"dest"`
IsComment bool `json:"was_comment"`
}
type inboxListing struct {
inboxThings
after string
before string
}
var _ anchor = &inboxListing{}
func (l *inboxListing) After() string {
return l.after
}
func (l *inboxListing) Before() string {
return l.before
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (l *inboxListing) UnmarshalJSON(b []byte) error {
root := new(struct {
Data struct {
Things inboxThings `json:"children"`
After string `json:"after"`
Before string `json:"before"`
} `json:"data"`
})
err := json.Unmarshal(b, root)
if err != nil {
return err
}
l.inboxThings = root.Data.Things
l.after = root.Data.After
l.before = root.Data.Before
return nil
}
// The returned JSON for comments is a bit different.
// It looks for like the Message struct.
type inboxThings struct {
Comments []*Message
Messages []*Message
}
// init initializes or clears the inbox.
func (t *inboxThings) init() {
t.Comments = make([]*Message, 0)
t.Messages = make([]*Message, 0)
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (t *inboxThings) UnmarshalJSON(b []byte) error {
t.init()
var things []thing
if err := json.Unmarshal(b, &things); err != nil {
return err
}
for _, thing := range things {
switch thing.Kind {
case kindComment:
v := new(Message)
if err := json.Unmarshal(thing.Data, v); err == nil {
t.Comments = append(t.Comments, v)
}
case kindMessage:
v := new(Message)
if err := json.Unmarshal(thing.Data, v); err == nil {
t.Messages = append(t.Messages, v)
}
}
}
return nil
}
// SendMessageRequest represents a request to send a message.
type SendMessageRequest struct {
// Username, or /r/name for that subreddit's moderators.
To string `url:"to"`
Subject string `url:"subject"`
Text string `url:"text"`
// Optional. If specified, the message will look like it came from the subreddit.
FromSubreddit string `url:"from_sr,omitempty"`
}
// ReadAll marks all messages/comments as read. It queues up the task on Reddit's end.
// A successful response returns 202 to acknowledge acceptance of the request.
// This endpoint is heavily rate limited.
func (s *MessageService) ReadAll(ctx context.Context) (*Response, error) {
path := "api/read_all_messages"
req, err := s.client.NewRequest(http.MethodPost, path, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// Read marks a message/comment as read via its full ID.
func (s *MessageService) Read(ctx context.Context, ids ...string) (*Response, error) {
if len(ids) == 0 {
return nil, errors.New("must provide at least 1 id")
}
path := "api/read_message"
form := url.Values{}
form.Set("id", strings.Join(ids, ","))
req, err := s.client.NewRequestWithForm(http.MethodPost, path, form)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// Unread marks a message/comment as unread via its full ID.
func (s *MessageService) Unread(ctx context.Context, ids ...string) (*Response, error) {
if len(ids) == 0 {
return nil, errors.New("must provide at least 1 id")
}
path := "api/unread_message"
form := url.Values{}
form.Set("id", strings.Join(ids, ","))
req, err := s.client.NewRequestWithForm(http.MethodPost, path, form)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// Block the author of a post, comment or message via its full ID.
func (s *MessageService) Block(ctx context.Context, id string) (*Response, error) {
path := "api/block"
form := url.Values{}
form.Set("id", id)
req, err := s.client.NewRequestWithForm(http.MethodPost, path, form)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// Collapse messages.
func (s *MessageService) Collapse(ctx context.Context, ids ...string) (*Response, error) {
if len(ids) == 0 {
return nil, errors.New("must provide at least 1 id")
}
path := "api/collapse_message"
form := url.Values{}
form.Set("id", strings.Join(ids, ","))
req, err := s.client.NewRequestWithForm(http.MethodPost, path, form)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// Uncollapse messages.
func (s *MessageService) Uncollapse(ctx context.Context, ids ...string) (*Response, error) {
if len(ids) == 0 {
return nil, errors.New("must provide at least 1 id")
}
path := "api/uncollapse_message"
form := url.Values{}
form.Set("id", strings.Join(ids, ","))
req, err := s.client.NewRequestWithForm(http.MethodPost, path, form)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// Delete a message.
func (s *MessageService) Delete(ctx context.Context, id string) (*Response, error) {
path := "api/del_msg"
form := url.Values{}
form.Set("id", id)
req, err := s.client.NewRequestWithForm(http.MethodPost, path, form)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// Send a message.
func (s *MessageService) Send(ctx context.Context, sendRequest *SendMessageRequest) (*Response, error) {
if sendRequest == nil {
return nil, errors.New("sendRequest: cannot be nil")
}
path := "api/compose"
form, err := query.Values(sendRequest)
if err != nil {
return nil, err
}
form.Set("api_type", "json")
req, err := s.client.NewRequestWithForm(http.MethodPost, path, form)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// Inbox returns comments and messages that appear in your inbox, respectively.
func (s *MessageService) Inbox(ctx context.Context, opts *ListOptions) ([]*Message, []*Message, *Response, error) {
root, resp, err := s.inbox(ctx, "message/inbox", opts)
if err != nil {
return nil, nil, resp, err
}
return root.Comments, root.Messages, resp, nil
}
// InboxUnread returns unread comments and messages that appear in your inbox, respectively.
func (s *MessageService) InboxUnread(ctx context.Context, opts *ListOptions) ([]*Message, []*Message, *Response, error) {
root, resp, err := s.inbox(ctx, "message/unread", opts)
if err != nil {
return nil, nil, resp, err
}
return root.Comments, root.Messages, resp, nil
}
// Sent returns messages that you've sent.
func (s *MessageService) Sent(ctx context.Context, opts *ListOptions) ([]*Message, *Response, error) {
root, resp, err := s.inbox(ctx, "message/sent", opts)
if err != nil {
return nil, resp, err
}
return root.Messages, resp, nil
}
func (s *MessageService) inbox(ctx context.Context, path string, opts *ListOptions) (*inboxListing, *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(inboxListing)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, nil, err
}
return root, resp, nil
}