Comments

Comments support voting, ranking, threading, images, URL previews, mentions and notifications.

Adding Comments

// Get feed instance
feed := client.Feeds().Feed("user", "john")
feedResponse, err := feed.GetOrCreate(context.Background(), &getstream.GetOrCreateFeedRequest{
  UserID:          getstream.PtrTo("john"),
})
activity := feedResponse.Data.Activities[0]
activityID := activity.ID
commentID := "6b6dd9df-13b0-4b47-8d73-a45a00d04fa7"
// folderID := "11e7d97a-e461-47f0-8d24-cd8a86168a60"

// Adding a comment to an activity
_, err = client.Feeds().AddComment(context.Background(), &getstream.AddCommentRequest{
  ID:         getstream.PtrTo("comment_123"), // ID is optional, but must be unique. will be auto-generated if not provided
  Comment:    "So great!",
  ObjectID:   activityID,
  ObjectType: "activity",
  UserID:     getstream.PtrTo("john"),
  Custom: map[string]any{
    "sentiment": "positive",
  },
})
if err != nil {
  log.Fatal("Error adding comment:", err)
}

// Adding a reply to a comment
_, err = client.Feeds().AddComment(context.Background(), &getstream.AddCommentRequest{
  Comment:  "I agree!",
  ParentID: getstream.PtrTo(commentID),
  UserID:   getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal("Error adding reply:", err)
}

// Adding a comment without triggering push notifications
_, err = client.Feeds().AddComment(context.Background(), &getstream.AddCommentRequest{
  Comment:    "Silent comment",
  ObjectID:   activityID,
  ObjectType: "activity",
  UserID:     getstream.PtrTo("john"),
  SkipPush:   getstream.PtrTo(true),
})
if err != nil {
  log.Fatal("Error adding silent comment:", err)
}

// Adding a comment with attachments
_, err = client.Feeds().AddComment(context.Background(), &getstream.AddCommentRequest{
  Comment:  "Check out this image!",
  ObjectID: activityID,
  ObjectType: "activity",
  UserID:   getstream.PtrTo("john"),
  Attachments: []getstream.Attachment{
    {
      AssetUrl: getstream.PtrTo("https://example.com/image.png"),
      Type:     getstream.PtrTo("image"),
      Title:    getstream.PtrTo("Image"),
      Custom:   map[string]any{},
    },
  },
})
if err != nil {
  log.Fatal("Error adding comment with attachment:", err)
}

You can use Stream's CDN for storing and quickly accessing files attached to comments.

Comment request options

Updating Comments

updateComment endpoint performs a partial update: only the fields you include in the request are changed, and each of those fields is completely overwritten.

updatedComment, err := client.Feeds().UpdateComment(context.Background(), commentID, &getstream.UpdateCommentRequest{
  Comment: getstream.PtrTo("Updated comment"),
})
if err != nil {
  log.Fatal("Error updating comment:", err)
}
log.Println("Comment edited at:", updatedComment.EditedAt)

Removing Comments

_, err = client.Feeds().DeleteComment(context.Background(), commentID, &getstream.DeleteCommentRequest{})
if err != nil {
  log.Fatal("Error deleting comment:", err)
}

Reading Comments

You'll also want to show/return these comments. The most important is when reading the feed.

feed := client.Feeds().Feed("user", "john")
feedResponse, err := feed.GetOrCreate(context.Background(), &getstream.GetOrCreateFeedRequest{
  UserID: getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal(err)
}

// Access comments from the first activity
activity := feedResponse.Data.Activities[0]
comments := activity.Comments

// Or using the getComments endpoint
commentsResponse, err := client.Feeds().GetComments(context.Background(), &getstream.GetCommentsRequest{
  ObjectType: "activity",
  ObjectID:   "123",
  Limit:      getstream.PtrTo(10),
  Sort:       getstream.PtrTo("best"),
})
if err != nil {
  log.Fatal(err)
}

commentsFromEndpoint := commentsResponse.Data.Comments

// Reading a single comment
comment, err := client.Feeds().GetComment(context.Background(), "123", &getstream.GetCommentRequest{})
if err != nil {
  log.Fatal(err)
}

Sort options

The following sort options are supported when reading comments:

  • last: newest comment returned first
  • first: oldest comment returned first
  • top: highest score returned first - computed from upvote - downvote reaction types
  • controversial: controversial comment returned first - mixed reactions (upvote and downvote) and lots of comments
  • best: highest Wilson score returned first - Wilson score balances reaction score (upvote - downvote) with number of reactions (a comment with lower reaction score but more sum reactions is returned before a comment with higher score, but few sum reactions)

Search for Comments

You can also query the comments, the following examples show

  • how to search across all comments by text
  • query all comments of a given user
// Search in comment texts
_, err = client.Feeds().QueryComments(context.Background(), &getstream.QueryCommentsRequest{
  Filter: map[string]any{
    "comment_text": map[string]any{
      "$q": "oat",
    },
  },
})
if err != nil {
  log.Printf("Error searching comments: %v", err)
}

// Comments by user
_, err = client.Feeds().QueryComments(context.Background(), &getstream.QueryCommentsRequest{
  Filter: map[string]any{
    "user_id": "jane",
  },
  Limit: getstream.PtrTo(20),
})
if err != nil {
  log.Printf("Error querying user comments: %v", err)
}

Comment Queryable Built-In Fields

nametypedescriptionsupported operationsexample
idstring or list of stringsThe ID of the comment$in, $eq{ id: { $in: [ 'comment_123', 'comment_456' ] } }
user_idstring or list of stringsThe ID of the user who created the comment$in, $eq{ user_id: { $eq: 'user_123' } }
object_typestring or list of stringsThe type of object being commented on$eq, $ne, $in, $nin{ object_type: { $in: [ 'activity', 'post' ] } }
object_idstring or list of stringsThe ID of the object being commented on$in, $eq{ object_id: { $eq: 'activity_123' } }
parent_idstring or list of stringsThe parent comment ID for replies$in, $eq{ parent_id: { $eq: 'comment_parent_123' } }
comment_textstringThe text content of the comment$q{ comment_text: { $q: 'search terms' } }
statusstring or list of stringsThe status of the comment$eq, $ne, $in{ status: { $in: [ 'approved', 'pending' ] } }
reply_countnumberThe number of replies to this comment$gt, $gte, $lt, $lte{ reply_count: { $gte: 5 } }
upvote_countnumberThe number of upvotes on the comment$gt, $gte, $lt, $lte{ upvote_count: { $gte: 10 } }
downvote_countnumberThe number of downvotes on the comment$gt, $gte, $lt, $lte{ downvote_count: { $lt: 5 } }
scorenumberThe overall score of the comment$gt, $gte, $lt, $lte{ score: { $gte: 0 } }
confidence_scorenumberThe confidence score of the comment$gt, $gte, $lt, $lte{ confidence_score: { $gte: 0.5 } }
controversy_scorenumberThe controversy score of the comment$gt, $gte, $lt, $lte{ controversy_score: { $lt: 0.8 } }
created_atstring, must be formatted as an RFC3339 timestampThe time the comment was created$eq, $gt, $gte, $lt, $lte{ created_at: { $gte: '2023-12-04T09:30:20.45Z' } }

Comment sort options

All fields support 1 and -1 directions.

Comment Reactions

When adding reactions and the enforce_unique flag is set to true, the existing reaction of a user will be overridden with the new reaction. Use this flag if you want to ensure users have a single reaction per each comment/activity. The default value is false, in which case users can have multiple reactions.

// Add comment reaction
_, err = client.Feeds().AddCommentReaction(context.Background(), commentID, &getstream.AddCommentReactionRequest{
  Type:   "like",
  UserID: getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal("Error adding comment reaction:", err)
}

// Delete comment reaction
_, err = client.Feeds().DeleteCommentReaction(context.Background(), commentID, "like", &getstream.DeleteCommentReactionRequest{
  UserID: getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal("Error deleting comment reaction:", err)
}

Overview of the reaction model

Comment Threading

// Get comments for an activity
	_, err = client.Feeds().GetComments(context.Background(), &getstream.GetCommentsRequest{
  ObjectID:   "activity_123",
  ObjectType: "activity",
  // Depth of the threaded comments
  Depth: getstream.PtrTo(3),
  Limit: getstream.PtrTo(20),
})
if err != nil {
  log.Fatal("Error getting comments:", err)
}

// Get replies of a specific parent comment
_, err = client.Feeds().GetCommentReplies(context.Background(), parentID, &getstream.GetCommentRepliesRequest{})
if err != nil {
  log.Fatal("Error getting comment replies:", err)
}

Reading comment replies supports the same sort parameters as reading comments does.

Restricting comment replies

Set who can add comments

When creating an activity it's possible to set who can add comments. Possible options:

  • Everyone (this is the default setting)
  • Only people I follow
  • Nobody

The activity author is always allowed to add comments regardless of the reply settings.

feedsClient := client.Feeds()
response, err := feedsClient.AddActivity(context.Background(), &getstream.AddActivityRequest{
	Type:          "post",
	Feeds:         []string{"user:john"},
	Text:          getstream.PtrTo("apple stock will go up"),
	UserID:        getstream.PtrTo("john"),
	RestrictReplies: getstream.PtrTo("people_i_follow"), // Options: "everyone", "people_i_follow", "nobody"
})
if err != nil {
	log.Fatal("Error adding activity:", err)
}

Show/hide comment input

If a user isn't allowed to add comments, it's a good idea to hide the comment input.

When everyone/nobody can comment this is straightforward. However if only people the activity author follows can comment, we have to check if there are any feeds owned by the current user that is followed by the activity author, the code snippet below shows how to check this:

const activity: ActivityResponse = /* fetch a single activity or read a feed */;

// Lists follow relationships where
// - target feed is owned by current user
// - source feed is owned by activity author
const feed = client.feed(
  activity.current_feed.group_id,
  activity.current_feed.id,
);
console.log(feed.currentState.own_followings);

own_followings is only set when reading a feed/activity from client-side, for server-side requests it will be empty.

  • When fetching a single activity, own_followings is set by default
  • When reading a feed, you have to explicitly enable fetching own_followings:
feed.getOrCreate({ enrichment_options: { enrich_own_followings: true } });

Overview of the comment model

Add comments in batch

_, err = client.Feeds().AddCommentsBatch(context.Background(), &getstream.AddCommentsBatchRequest{
  Comments: []getstream.AddCommentRequest{
    {
      ID:         getstream.PtrTo("comment_123"), // ID is optional, but must be unique. will be auto-generated if not provided
      Comment:    "So great!",
      ObjectID:   "activity_123",
      ObjectType: "activity",
      UserID:     getstream.PtrTo("john"),
      Custom: map[string]any{
        "sentiment": "positive",
      },
    },
  },
})
if err != nil {
  log.Fatal("Error adding comments batch:", err)
}