Move everything to new reddit/ folder
Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
package reddit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
const (
|
||||
kindComment = "t1"
|
||||
kindAccount = "t2"
|
||||
kindPost = "t3"
|
||||
kindMessage = "t4"
|
||||
kindSubreddit = "t5"
|
||||
kindAward = "t6"
|
||||
kindListing = "Listing"
|
||||
kindKarmaList = "KarmaList"
|
||||
kindTrophyList = "TrophyList"
|
||||
kindUserList = "UserList"
|
||||
kindMore = "more"
|
||||
kindModAction = "modaction"
|
||||
)
|
||||
|
||||
// thing is an entity on Reddit.
|
||||
// Its kind reprsents what it is and what is stored in the Data field
|
||||
// e.g. t1 = comment, t2 = user, t3 = post, etc.
|
||||
type thing struct {
|
||||
Kind string `json:"kind"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
type rootListing struct {
|
||||
Kind string `json:"kind"`
|
||||
Data listing `json:"data"`
|
||||
}
|
||||
|
||||
// listing holds things coming from the Reddit API
|
||||
// It also contains the after/before anchors useful for subsequent requests
|
||||
type listing struct {
|
||||
Things things `json:"children"`
|
||||
After string `json:"after"`
|
||||
Before string `json:"before"`
|
||||
}
|
||||
|
||||
type things struct {
|
||||
Comments []*Comment
|
||||
Mores []*More
|
||||
Users []*User
|
||||
Posts []*Post
|
||||
Subreddits []*Subreddit
|
||||
ModActions []*ModAction
|
||||
}
|
||||
|
||||
// init initializes or clears the listing.
|
||||
func (t *things) init() {
|
||||
t.Comments = make([]*Comment, 0)
|
||||
t.Mores = make([]*More, 0)
|
||||
t.Users = make([]*User, 0)
|
||||
t.Posts = make([]*Post, 0)
|
||||
t.Subreddits = make([]*Subreddit, 0)
|
||||
t.ModActions = make([]*ModAction, 0)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (t *things) 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(Comment)
|
||||
if err := json.Unmarshal(thing.Data, v); err == nil {
|
||||
t.Comments = append(t.Comments, v)
|
||||
}
|
||||
case kindMore:
|
||||
v := new(More)
|
||||
if err := json.Unmarshal(thing.Data, v); err == nil {
|
||||
t.Mores = append(t.Mores, v)
|
||||
}
|
||||
case kindAccount:
|
||||
v := new(User)
|
||||
if err := json.Unmarshal(thing.Data, v); err == nil {
|
||||
t.Users = append(t.Users, v)
|
||||
}
|
||||
case kindPost:
|
||||
v := new(Post)
|
||||
if err := json.Unmarshal(thing.Data, v); err == nil {
|
||||
t.Posts = append(t.Posts, v)
|
||||
}
|
||||
case kindSubreddit:
|
||||
v := new(Subreddit)
|
||||
if err := json.Unmarshal(thing.Data, v); err == nil {
|
||||
t.Subreddits = append(t.Subreddits, v)
|
||||
}
|
||||
case kindModAction:
|
||||
v := new(ModAction)
|
||||
if err := json.Unmarshal(thing.Data, v); err == nil {
|
||||
t.ModActions = append(t.ModActions, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Comment is a comment posted by a user.
|
||||
type Comment struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
FullID string `json:"name,omitempty"`
|
||||
Created *Timestamp `json:"created_utc,omitempty"`
|
||||
Edited *Timestamp `json:"edited,omitempty"`
|
||||
|
||||
ParentID string `json:"parent_id,omitempty"`
|
||||
Permalink string `json:"permalink,omitempty"`
|
||||
|
||||
Body string `json:"body,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
AuthorID string `json:"author_fullname,omitempty"`
|
||||
AuthorFlairText string `json:"author_flair_text,omitempty"`
|
||||
AuthorFlairID string `json:"author_flair_template_id,omitempty"`
|
||||
|
||||
SubredditName string `json:"subreddit,omitempty"`
|
||||
SubredditNamePrefixed string `json:"subreddit_name_prefixed,omitempty"`
|
||||
SubredditID string `json:"subreddit_id,omitempty"`
|
||||
|
||||
// Indicates if you've upvote/downvoted (true/false).
|
||||
// If neither, it will be nil.
|
||||
Likes *bool `json:"likes"`
|
||||
|
||||
Score int `json:"score"`
|
||||
Controversiality int `json:"controversiality"`
|
||||
|
||||
PostID string `json:"link_id,omitempty"`
|
||||
// This doesn't appear consistently.
|
||||
PostTitle string `json:"link_title,omitempty"`
|
||||
// This doesn't appear consistently.
|
||||
PostPermalink string `json:"link_permalink,omitempty"`
|
||||
// This doesn't appear consistently.
|
||||
PostAuthor string `json:"link_author,omitempty"`
|
||||
// This doesn't appear consistently.
|
||||
PostNumComments *int `json:"num_comments,omitempty"`
|
||||
|
||||
IsSubmitter bool `json:"is_submitter"`
|
||||
ScoreHidden bool `json:"score_hidden"`
|
||||
Saved bool `json:"saved"`
|
||||
Stickied bool `json:"stickied"`
|
||||
Locked bool `json:"locked"`
|
||||
CanGild bool `json:"can_gild"`
|
||||
NSFW bool `json:"over_18"`
|
||||
|
||||
Replies Replies `json:"replies"`
|
||||
}
|
||||
|
||||
// HasMore determines whether the comment has more replies to load in its reply tree.
|
||||
func (c *Comment) HasMore() bool {
|
||||
return c.Replies.More != nil && len(c.Replies.More.Children) > 0
|
||||
}
|
||||
|
||||
// addCommentToReplies traverses the comment tree to find the one
|
||||
// that the 2nd comment is replying to. It then adds it to its replies.
|
||||
func (c *Comment) addCommentToReplies(comment *Comment) {
|
||||
if c.FullID == comment.ParentID {
|
||||
c.Replies.Comments = append(c.Replies.Comments, comment)
|
||||
return
|
||||
}
|
||||
|
||||
for _, reply := range c.Replies.Comments {
|
||||
reply.addCommentToReplies(comment)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Comment) addMoreToReplies(more *More) {
|
||||
if c.FullID == more.ParentID {
|
||||
c.Replies.More = more
|
||||
return
|
||||
}
|
||||
|
||||
for _, reply := range c.Replies.Comments {
|
||||
reply.addMoreToReplies(more)
|
||||
}
|
||||
}
|
||||
|
||||
// Replies holds replies to a comment.
|
||||
// It contains both comments and "more" comments, which are entrypoints to other
|
||||
// comments that were left out.
|
||||
type Replies struct {
|
||||
Comments []*Comment `json:"comments,omitempty"`
|
||||
More *More `json:"-"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (r *Replies) UnmarshalJSON(data []byte) error {
|
||||
// if a comment has no replies, its "replies" field is set to ""
|
||||
if string(data) == `""` {
|
||||
r = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
root := new(rootListing)
|
||||
err := json.Unmarshal(data, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Comments = root.Data.Things.Comments
|
||||
r.More = root.getFirstMore()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (r *Replies) MarshalJSON() ([]byte, error) {
|
||||
if r == nil || len(r.Comments) == 0 {
|
||||
return []byte(`null`), nil
|
||||
}
|
||||
return json.Marshal(r.Comments)
|
||||
}
|
||||
|
||||
// More holds information used to retrieve additional comments omitted from a base comment tree.
|
||||
type More struct {
|
||||
ID string `json:"id"`
|
||||
FullID string `json:"name"`
|
||||
ParentID string `json:"parent_id"`
|
||||
// Total number of replies to the parent + replies to those replies (recursively).
|
||||
Count int `json:"count"`
|
||||
// Number of comment nodes from the parent down to the furthest comment node.
|
||||
Depth int `json:"depth"`
|
||||
Children []string `json:"children"`
|
||||
}
|
||||
|
||||
// Post is a submitted post on Reddit.
|
||||
type Post struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
FullID string `json:"name,omitempty"`
|
||||
Created *Timestamp `json:"created_utc,omitempty"`
|
||||
Edited *Timestamp `json:"edited,omitempty"`
|
||||
|
||||
Permalink string `json:"permalink,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
|
||||
Title string `json:"title,omitempty"`
|
||||
Body string `json:"selftext,omitempty"`
|
||||
|
||||
// Indicates if you've upvote/downvoted (true/false).
|
||||
// If neither, it will be nil.
|
||||
Likes *bool `json:"likes"`
|
||||
|
||||
Score int `json:"score"`
|
||||
UpvoteRatio float32 `json:"upvote_ratio"`
|
||||
NumberOfComments int `json:"num_comments"`
|
||||
|
||||
SubredditName string `json:"subreddit,omitempty"`
|
||||
SubredditNamePrefixed string `json:"subreddit_name_prefixed,omitempty"`
|
||||
SubredditID string `json:"subreddit_id,omitempty"`
|
||||
|
||||
Author string `json:"author,omitempty"`
|
||||
AuthorID string `json:"author_fullname,omitempty"`
|
||||
|
||||
Spoiler bool `json:"spoiler"`
|
||||
Locked bool `json:"locked"`
|
||||
NSFW bool `json:"over_18"`
|
||||
IsSelfPost bool `json:"is_self"`
|
||||
Saved bool `json:"saved"`
|
||||
Stickied bool `json:"stickied"`
|
||||
}
|
||||
|
||||
// Subreddit holds information about a subreddit
|
||||
type Subreddit struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
FullID string `json:"name,omitempty"`
|
||||
Created *Timestamp `json:"created_utc,omitempty"`
|
||||
|
||||
URL string `json:"url,omitempty"`
|
||||
Name string `json:"display_name,omitempty"`
|
||||
NamePrefixed string `json:"display_name_prefixed,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"public_description,omitempty"`
|
||||
Type string `json:"subreddit_type,omitempty"`
|
||||
SuggestedCommentSort string `json:"suggested_comment_sort,omitempty"`
|
||||
|
||||
Subscribers int `json:"subscribers"`
|
||||
ActiveUserCount *int `json:"active_user_count,omitempty"`
|
||||
NSFW bool `json:"over18"`
|
||||
UserIsMod bool `json:"user_is_moderator"`
|
||||
Subscribed bool `json:"user_is_subscriber"`
|
||||
Favorite bool `json:"user_has_favorited"`
|
||||
}
|
||||
|
||||
func (l *rootListing) getComments() *Comments {
|
||||
return &Comments{
|
||||
Comments: l.Data.Things.Comments,
|
||||
After: l.Data.After,
|
||||
Before: l.Data.Before,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *rootListing) getFirstMore() *More {
|
||||
if len(l.Data.Things.Mores) == 0 {
|
||||
return nil
|
||||
}
|
||||
return l.Data.Things.Mores[0]
|
||||
}
|
||||
|
||||
func (l *rootListing) getUsers() *Users {
|
||||
return &Users{
|
||||
Users: l.Data.Things.Users,
|
||||
After: l.Data.After,
|
||||
Before: l.Data.Before,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *rootListing) getPosts() *Posts {
|
||||
return &Posts{
|
||||
Posts: l.Data.Things.Posts,
|
||||
After: l.Data.After,
|
||||
Before: l.Data.Before,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *rootListing) getSubreddits() *Subreddits {
|
||||
return &Subreddits{
|
||||
Subreddits: l.Data.Things.Subreddits,
|
||||
After: l.Data.After,
|
||||
Before: l.Data.Before,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *rootListing) getModActions() *ModActions {
|
||||
return &ModActions{
|
||||
ModActions: l.Data.Things.ModActions,
|
||||
After: l.Data.After,
|
||||
Before: l.Data.Before,
|
||||
}
|
||||
}
|
||||
|
||||
// Comments is a list of comments
|
||||
type Comments struct {
|
||||
Comments []*Comment `json:"comments"`
|
||||
After string `json:"after"`
|
||||
Before string `json:"before"`
|
||||
}
|
||||
|
||||
// Users is a list of users
|
||||
type Users struct {
|
||||
Users []*User `json:"users"`
|
||||
After string `json:"after"`
|
||||
Before string `json:"before"`
|
||||
}
|
||||
|
||||
// Subreddits is a list of subreddits
|
||||
type Subreddits struct {
|
||||
Subreddits []*Subreddit `json:"subreddits"`
|
||||
After string `json:"after"`
|
||||
Before string `json:"before"`
|
||||
}
|
||||
|
||||
// Posts is a list of posts.
|
||||
type Posts struct {
|
||||
Posts []*Post `json:"posts"`
|
||||
After string `json:"after"`
|
||||
Before string `json:"before"`
|
||||
}
|
||||
|
||||
// ModActions is a list of moderator actions.
|
||||
type ModActions struct {
|
||||
ModActions []*ModAction `json:"moderator_actions"`
|
||||
After string `json:"after"`
|
||||
Before string `json:"before"`
|
||||
}
|
||||
|
||||
// PostAndComments is a post and its comments.
|
||||
type PostAndComments struct {
|
||||
Post *Post `json:"post"`
|
||||
Comments []*Comment `json:"comments"`
|
||||
More *More `json:"-"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
// When getting a sticky post, you get an array of 2 Listings
|
||||
// The 1st one contains the single post in its children array
|
||||
// The 2nd one contains the comments to the post
|
||||
func (pc *PostAndComments) UnmarshalJSON(data []byte) error {
|
||||
var l [2]rootListing
|
||||
|
||||
err := json.Unmarshal(data, &l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
post := l[0].getPosts().Posts[0]
|
||||
comments := l[1].getComments().Comments
|
||||
moreComments := l[1].getFirstMore()
|
||||
|
||||
pc.Post = post
|
||||
pc.Comments = comments
|
||||
pc.More = moreComments
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasMore determines whether the post has more replies to load in its reply tree.
|
||||
func (pc *PostAndComments) HasMore() bool {
|
||||
return pc.More != nil && len(pc.More.Children) > 0
|
||||
}
|
||||
|
||||
func (pc *PostAndComments) addCommentToTree(comment *Comment) {
|
||||
if pc.Post.FullID == comment.ParentID {
|
||||
pc.Comments = append(pc.Comments, comment)
|
||||
return
|
||||
}
|
||||
|
||||
for _, reply := range pc.Comments {
|
||||
reply.addCommentToReplies(comment)
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PostAndComments) addMoreToTree(more *More) {
|
||||
if pc.Post.FullID == more.ParentID {
|
||||
pc.More = more
|
||||
}
|
||||
|
||||
for _, reply := range pc.Comments {
|
||||
reply.addMoreToReplies(more)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user