Finish MessageService

Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
This commit is contained in:
Vartan Benohanian 2020-08-17 21:13:33 -04:00
parent 1908dedd27
commit ac2fe30647
4 changed files with 371 additions and 2 deletions

View File

@ -2,10 +2,13 @@ package reddit
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/url"
"strings"
"github.com/google/go-querystring/query"
)
// MessageService handles communication with the message
@ -16,6 +19,106 @@ 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"`
}
// Messages is a list of messages.
type Messages struct {
Messages []*Message `json:"messages"`
After string `json:"after"`
Before string `json:"before"`
}
type rootInboxListing struct {
Kind string `json:"kind"`
Data inboxListing `json:"data"`
}
type inboxListing struct {
Things inboxThings `json:"children"`
After string `json:"after"`
Before string `json:"before"`
}
// 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
}
func (l *rootInboxListing) getComments() *Messages {
return &Messages{
Messages: l.Data.Things.Comments,
After: l.Data.After,
Before: l.Data.Before,
}
}
func (l *rootInboxListing) getMessages() *Messages {
return &Messages{
Messages: l.Data.Things.Messages,
After: l.Data.After,
Before: l.Data.Before,
}
}
// 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.
@ -136,3 +239,72 @@ func (s *MessageService) Delete(ctx context.Context, id string) (*Response, erro
return s.client.Do(ctx, req, nil)
}
// Send sends 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) (*Messages, *Messages, *Response, error) {
root, resp, err := s.inbox(ctx, "message/inbox", opts)
if err != nil {
return nil, nil, resp, err
}
return root.getComments(), root.getMessages(), resp, nil
}
// InboxUnread returns unread comments and messages that appear in your inbox, respectively.
func (s *MessageService) InboxUnread(ctx context.Context, opts *ListOptions) (*Messages, *Messages, *Response, error) {
root, resp, err := s.inbox(ctx, "message/unread", opts)
if err != nil {
return nil, nil, resp, err
}
return root.getComments(), root.getMessages(), resp, nil
}
// Sent returns messages that you've sent.
func (s *MessageService) Sent(ctx context.Context, opts *ListOptions) (*Messages, *Response, error) {
root, resp, err := s.inbox(ctx, "message/sent", opts)
if err != nil {
return nil, resp, err
}
return root.getMessages(), resp, nil
}
func (s *MessageService) inbox(ctx context.Context, path string, opts *ListOptions) (*rootInboxListing, *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(rootInboxListing)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, nil, err
}
return root, resp, nil
}

View File

@ -1,13 +1,57 @@
package reddit
import (
"fmt"
"net/http"
"net/url"
"testing"
"time"
"github.com/stretchr/testify/require"
)
var expectedCommentMessages = &Messages{
Messages: []*Message{
{
ID: "g1xi2m9",
FullID: "t1_g1xi2m9",
Created: &Timestamp{time.Date(2020, 8, 18, 0, 24, 13, 0, time.UTC)},
Subject: "post reply",
Text: "u/testuser2 hello",
ParentID: "t3_hs03f3",
Author: "testuser1",
To: "testuser2",
IsComment: true,
},
},
After: "",
Before: "",
}
var expectedMessages = &Messages{
Messages: []*Message{
{
ID: "qwki97",
FullID: "t4_qwki97",
Created: &Timestamp{time.Date(2020, 8, 18, 0, 16, 53, 0, time.UTC)},
Subject: "re: test",
Text: "test",
ParentID: "t4_qwki4m",
Author: "testuser1",
To: "testuser2",
IsComment: false,
},
},
After: "",
Before: "",
}
func TestMessageService_ReadAll(t *testing.T) {
setup()
defer teardown()
@ -147,3 +191,87 @@ func TestMessageService_Delete(t *testing.T) {
_, err := client.Message.Delete(ctx, "test")
require.NoError(t, err)
}
func TestMessageService_Send(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/api/compose", func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
form := url.Values{}
form.Set("api_type", "json")
form.Set("to", "test")
form.Set("subject", "test subject")
form.Set("text", "test text")
form.Set("from_sr", "hello world")
err := r.ParseForm()
require.NoError(t, err)
require.Equal(t, form, r.Form)
})
_, err := client.Message.Send(ctx, nil)
require.EqualError(t, err, "sendRequest: cannot be nil")
_, err = client.Message.Send(ctx, &SendMessageRequest{
To: "test",
Subject: "test subject",
Text: "test text",
FromSubreddit: "hello world",
})
require.NoError(t, err)
}
func TestMessageService_Inbox(t *testing.T) {
setup()
defer teardown()
blob, err := readFileContents("testdata/message/inbox.json")
require.NoError(t, err)
mux.HandleFunc("/message/inbox", func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodGet, r.Method)
fmt.Fprint(w, blob)
})
comments, messages, _, err := client.Message.Inbox(ctx, nil)
require.NoError(t, err)
require.Equal(t, expectedCommentMessages, comments)
require.Equal(t, expectedMessages, messages)
}
func TestMessageService_InboxUnread(t *testing.T) {
setup()
defer teardown()
blob, err := readFileContents("testdata/message/inbox.json")
require.NoError(t, err)
mux.HandleFunc("/message/unread", func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodGet, r.Method)
fmt.Fprint(w, blob)
})
comments, messages, _, err := client.Message.InboxUnread(ctx, nil)
require.NoError(t, err)
require.Equal(t, expectedCommentMessages, comments)
require.Equal(t, expectedMessages, messages)
}
func TestMessageService_Sent(t *testing.T) {
setup()
defer teardown()
blob, err := readFileContents("testdata/message/inbox.json")
require.NoError(t, err)
mux.HandleFunc("/message/sent", func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodGet, r.Method)
fmt.Fprint(w, blob)
})
messages, _, err := client.Message.Sent(ctx, nil)
require.NoError(t, err)
require.Equal(t, expectedMessages, messages)
}

70
testdata/message/inbox.json vendored Normal file
View File

@ -0,0 +1,70 @@
{
"kind": "Listing",
"data": {
"modhash": null,
"dist": 2,
"children": [
{
"kind": "t1",
"data": {
"first_message": null,
"first_message_name": null,
"subreddit": "helloworldtestt",
"likes": null,
"replies": "",
"id": "g1xi2m9",
"subject": "post reply",
"associated_awarding_id": null,
"score": 1,
"author": "testuser1",
"num_comments": 17,
"parent_id": "t3_hs03f3",
"subreddit_name_prefixed": "r/helloworldtestt",
"new": false,
"type": "post_reply",
"body": "u/testuser2 hello",
"link_title": "post 1",
"dest": "testuser2",
"was_comment": true,
"body_html": "&lt;!-- SC_OFF --&gt;&lt;div class=\"md\"&gt;&lt;p&gt;&lt;a href=\"/u/testuser2\"&gt;u/testuser2&lt;/a&gt; hello&lt;/p&gt;\n&lt;/div&gt;&lt;!-- SC_ON --&gt;",
"name": "t1_g1xi2m9",
"created": 1597739053.0,
"created_utc": 1597710253.0,
"context": "/r/helloworldtestt/comments/hs03f3/post_1/g1xi2m9/?context=3",
"distinguished": null
}
},
{
"kind": "t4",
"data": {
"first_message": 1626823824,
"first_message_name": "t4_qwkhao",
"subreddit": null,
"likes": null,
"replies": "",
"id": "qwki97",
"subject": "re: test",
"associated_awarding_id": null,
"score": 0,
"author": "testuser1",
"num_comments": null,
"parent_id": "t4_qwki4m",
"subreddit_name_prefixed": null,
"new": false,
"type": "unknown",
"body": "test",
"dest": "testuser2",
"body_html": "&lt;!-- SC_OFF --&gt;&lt;div class=\"md\"&gt;&lt;p&gt;test&lt;/p&gt;\n&lt;/div&gt;&lt;!-- SC_ON --&gt;",
"was_comment": false,
"name": "t4_qwki97",
"created": 1597738613.0,
"created_utc": 1597709813.0,
"context": "",
"distinguished": null
}
}
],
"after": "",
"before": null
}
}

View File

@ -106,7 +106,6 @@ func (t *things) UnmarshalJSON(b []byte) error {
if err := json.Unmarshal(thing.Data, v); err == nil {
t.Posts = append(t.Posts, v)
}
case kindMessage:
case kindSubreddit:
v := new(Subreddit)
if err := json.Unmarshal(thing.Data, v); err == nil {
@ -389,7 +388,7 @@ type Posts struct {
Before string `json:"before"`
}
// ModActions is a list of moderator action.
// ModActions is a list of moderator actions.
type ModActions struct {
ModActions []*ModAction `json:"moderator_actions"`
After string `json:"after"`