2024-04-10 17:13:07 +07:00
|
|
|
package reddit
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log/slog"
|
|
|
|
"net/http"
|
2024-05-02 23:16:28 +07:00
|
|
|
"strconv"
|
2024-04-10 22:38:19 +07:00
|
|
|
"strings"
|
2024-04-10 17:13:07 +07:00
|
|
|
|
|
|
|
"github.com/tigorlazuardi/redmage/pkg/errs"
|
|
|
|
)
|
|
|
|
|
|
|
|
type SubredditType int
|
|
|
|
|
2024-04-26 10:51:09 +07:00
|
|
|
func (su *SubredditType) UnmarshalJSON(b []byte) error {
|
2024-05-02 23:16:28 +07:00
|
|
|
if len(b) == 4 && string(b) == "null" {
|
2024-04-26 10:51:09 +07:00
|
|
|
return nil
|
2024-05-02 23:16:28 +07:00
|
|
|
}
|
|
|
|
s, err := strconv.Unquote(string(b))
|
|
|
|
if err != nil {
|
|
|
|
return errs.Wrapw(err, "failed to unquote string json value").Code(http.StatusBadRequest)
|
|
|
|
}
|
|
|
|
return su.Parse(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (su *SubredditType) Parse(s string) error {
|
|
|
|
switch s {
|
2024-04-26 10:51:09 +07:00
|
|
|
case `"user"`, `"u"`, "1":
|
|
|
|
*su = SubredditTypeUser
|
|
|
|
return nil
|
2024-05-02 23:16:28 +07:00
|
|
|
case `"r"`, `"subreddit"`, "0", "":
|
2024-04-26 10:51:09 +07:00
|
|
|
*su = SubredditTypeSub
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return errs.
|
2024-05-02 23:16:28 +07:00
|
|
|
Fail("subreddit type not recognized. Valid values are '' (empty), 'user', 'u', 'r', 'subreddit', 0, 1, and null",
|
|
|
|
"got", s,
|
2024-04-26 10:51:09 +07:00
|
|
|
).
|
|
|
|
Code(http.StatusBadRequest)
|
|
|
|
}
|
|
|
|
|
2024-04-10 17:13:07 +07:00
|
|
|
const (
|
|
|
|
SubredditTypeSub SubredditType = iota
|
|
|
|
SubredditTypeUser
|
|
|
|
)
|
|
|
|
|
|
|
|
func (s SubredditType) Code() string {
|
|
|
|
switch s {
|
|
|
|
case SubredditTypeUser:
|
|
|
|
return "user"
|
|
|
|
default:
|
|
|
|
return "r"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-26 10:51:09 +07:00
|
|
|
func (s SubredditType) String() string {
|
2024-05-27 21:10:37 +07:00
|
|
|
switch s {
|
|
|
|
case SubredditTypeUser:
|
|
|
|
return "User"
|
|
|
|
default:
|
|
|
|
return "Subreddit"
|
|
|
|
}
|
2024-04-26 10:51:09 +07:00
|
|
|
}
|
|
|
|
|
2024-04-10 17:13:07 +07:00
|
|
|
type GetPostsParam struct {
|
|
|
|
Subreddit string
|
|
|
|
Limit int
|
2024-04-25 12:31:20 +07:00
|
|
|
After string
|
2024-04-10 17:13:07 +07:00
|
|
|
SubredditType SubredditType
|
|
|
|
}
|
|
|
|
|
2024-04-10 22:38:19 +07:00
|
|
|
func (reddit *Reddit) GetPosts(ctx context.Context, params GetPostsParam) (posts Listing, err error) {
|
2024-04-26 10:51:09 +07:00
|
|
|
ctx, span := tracer.Start(ctx, "*Reddit.GetPosts")
|
|
|
|
defer span.End()
|
|
|
|
|
2024-04-25 12:31:20 +07:00
|
|
|
url := fmt.Sprintf("https://reddit.com/%s/%s.json?limit=%d&after=%s", params.SubredditType.Code(), params.Subreddit, params.Limit, params.After)
|
2024-04-10 17:13:07 +07:00
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
|
|
|
|
if err != nil {
|
|
|
|
return posts, errs.Wrapw(err, "reddit: failed to create http request instance", "url", url, "params", params)
|
|
|
|
}
|
|
|
|
|
2024-04-27 15:16:14 +07:00
|
|
|
req.Header.Set("User-Agent", reddit.Config.String("download.useragent"))
|
|
|
|
|
2024-04-10 17:13:07 +07:00
|
|
|
res, err := reddit.Client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return posts, errs.Wrapw(err, "reddit: failed to execute http request", "url", url, "params", params)
|
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
2024-04-26 22:13:04 +07:00
|
|
|
if res.StatusCode == http.StatusTooManyRequests {
|
2024-04-27 15:16:14 +07:00
|
|
|
return posts, errs.Fail("reddit: too many requests",
|
|
|
|
"retry_after", res.Header.Get("Retry-After"),
|
|
|
|
"url", url,
|
|
|
|
)
|
2024-04-26 22:13:04 +07:00
|
|
|
}
|
2024-04-10 17:13:07 +07:00
|
|
|
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
body, _ := io.ReadAll(res.Body)
|
|
|
|
return posts, errs.Fail("reddit: unexpected status code when executing GetPosts",
|
|
|
|
slog.Group("request", "url", url, "params", params),
|
2024-04-10 22:38:19 +07:00
|
|
|
slog.Group("response", "status_code", res.StatusCode, "body", formatLogBody(res, body)),
|
2024-04-14 00:32:55 +07:00
|
|
|
).Code(res.StatusCode)
|
2024-04-10 17:13:07 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
err = json.NewDecoder(res.Body).Decode(&posts)
|
|
|
|
if err != nil {
|
|
|
|
return posts, errs.Wrapw(err, "reddit: failed to parse response body when getting posts from reddit", "url", url, "params", params)
|
|
|
|
}
|
|
|
|
|
|
|
|
return posts, nil
|
|
|
|
}
|
2024-04-10 22:38:19 +07:00
|
|
|
|
|
|
|
func formatLogBody(res *http.Response, body []byte) any {
|
|
|
|
if strings.HasPrefix(res.Header.Get("Content-Type"), "application/json") {
|
|
|
|
return json.RawMessage(body)
|
|
|
|
}
|
|
|
|
return string(body)
|
|
|
|
}
|