Activity Feeds v3 is in beta — try it out!

Follows

Follow & Unfollow

The source feed should have a group that has “following” activity selector enabled, for example the built-in timeline group. The target feed should have a group that has “current” activity selector enabled, for example the built-in user group.

// Create timeline feed
timeline := client.Feeds().Feed("timeline", "john")
  _, err = timeline.GetOrCreate(context.Background(), &getstream.GetOrCreateFeedRequest{
  UserID: getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal("Error getting/creating timeline feed:", err)
}
log.Println("Timeline feed created/retrieved successfully")

// Follow a user
_, err = client.Feeds().Follow(context.Background(), &getstream.FollowRequest{
  Source: "timeline:john",
  Target: "user:tom",
})
if err != nil {
  log.Fatal("Error following user:", err)
}
log.Println("Successfully followed user:tom")

// Follow a stock
_, err = client.Feeds().Follow(context.Background(), &getstream.FollowRequest{
  Source: "timeline:john",
  Target: "stock:apple",
})
if err != nil {
  log.Fatal("Error following stock:", err)
}
log.Println("Successfully followed stock:apple")

// Follow with more fields
_, err = client.Feeds().Follow(context.Background(), &getstream.FollowRequest{
  Source:         "timeline:john",
  Target:         "stock:apple",
  PushPreference: getstream.PtrTo("all"),
  Custom: map[string]any{
    "reason": "investment",
  },
})
if err != nil {
  log.Fatal("Error following stock with custom fields:", err)
}
log.Println("Successfully followed stock:apple with custom fields")

Trying to follow a feed that is already followed will result in an error. Similarly, trying to unfollow a feed that is not followed, will result in an error.

When unfollowing a feed, all previous activities of that feed are removed from the timeline.

Querying Follows

ctx := context.Background()

// Create timeline feed
myTimeline := client.Feeds().Feed("timeline", "john")
_, err = myTimeline.GetOrCreate(ctx, &getstream.GetOrCreateFeedRequest{
  UserID: getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal("Error creating timeline feed:", err)
}

// Query follows to check if we follow a list of feeds
response, err := client.Feeds().QueryFollows(ctx, &getstream.QueryFollowsRequest{
  Filter: map[string]any{
    "source_feed": "timeline:john",
    "target_feed": map[string]any{
      "$in": []string{"user:sara", "user:adam"},
    },
  },
})
if err != nil {
  log.Fatal("Error querying follows:", err)
}
log.Printf("Follows: %+v", response.Data.Follows)

// Create user feed
userFeed := client.Feeds().Feed("user", "john")
_, err = userFeed.GetOrCreate(ctx, &getstream.GetOrCreateFeedRequest{
  UserID: getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal("Error creating user feed:", err)
}

// Paginating through followers for a feed - first page
firstPage, err := client.Feeds().QueryFollows(ctx, &getstream.QueryFollowsRequest{
  Filter: map[string]any{
    "target_feed": "user:john",
  },
  Limit: getstream.PtrTo(20),
})
if err != nil {
  log.Fatal("Error querying first page of follows:", err)
}

// Next page
secondPage, err := client.Feeds().QueryFollows(ctx, &getstream.QueryFollowsRequest{
  Filter: map[string]any{
    "target_feed": "user:john",
  },
  Limit: getstream.PtrTo(20),
  Next:  firstPage.Data.Next,
})
if err != nil {
  log.Fatal("Error querying second page of follows:", err)
}
log.Printf("First page follows: %+v", firstPage.Data.Follows)
log.Printf("Second page follows: %+v", secondPage.Data.Follows)

// Filter by source - feeds that I follow
sourceFollows, err := client.Feeds().QueryFollows(ctx, &getstream.QueryFollowsRequest{
  Filter: map[string]any{
    "source_feed": "timeline:john",
  },
  Limit: getstream.PtrTo(20),
})
if err != nil {
  log.Fatal("Error querying source follows:", err)
}
log.Printf("Source follows: %+v", sourceFollows.Data.Follows)

Follows Queryable Built-In Fields

nametypedescriptionsupported operationsexample
source_feedstring or list of stringsThe feed ID that is following$in, $eq{ source_feed: { $eq: 'messaging:general' } }
target_feedstring or list of stringsThe feed ID being followed$in, $eq{ target_feed: { $in: [ 'sports:news', 'tech:updates' ] } }
statusstring or list of stringsThe follow status$in, $eq{ status: { $in: [ 'accepted', 'pending', 'rejected' ] } }
created_atstring, must be formatted as an RFC3339 timestampThe time the follow relationship was created$eq, $gt, $gte, $lt, $lte{ created_at: { $gte: '2023-12-04T09:30:20.45Z' } }

Follow Requests

Some apps require the user’s approval for following them.

ctx := context.Background()

saraFeed := client.Feeds().Feed("user", "sara")
_, err = saraFeed.GetOrCreate(ctx, &getstream.GetOrCreateFeedRequest{
  Data: &getstream.FeedInput{
    Visibility: getstream.PtrTo("followers"),
  },
  UserID: getstream.PtrTo("sara"),
})
if err != nil {
  log.Fatal("Error creating sara feed:", err)
}

adamTimeline := client.Feeds().Feed("timeline", "adam")
_, err = adamTimeline.GetOrCreate(ctx, &getstream.GetOrCreateFeedRequest{
  UserID: getstream.PtrTo("adam"),
})
if err != nil {
  log.Fatal("Error creating adam timeline feed:", err)
}

// Create follow request from adamTimeline to saraFeed
followRequest, err := client.Feeds().Follow(ctx, &getstream.FollowRequest{
  Source: "timeline:adam",
  Target: "user:sara",
})
if err != nil {
  log.Fatal("Error creating follow request:", err)
}
fmt.Printf("Follow request status: %s\n", followRequest.Data.Follow.Status)

// Accept follow request and set follower's role
_, err = client.Feeds().AcceptFollow(ctx, &getstream.AcceptFollowRequest{
  Source:       "timeline:adam",
  Target:       "user:sara",
  FollowerRole: getstream.PtrTo("feed_member"),
})
if err != nil {
  log.Fatal("Error accepting follow request:", err)
}

// Reject follow request
_, err = client.Feeds().RejectFollow(ctx, &getstream.RejectFollowRequest{
  Source: "timeline:adam",
  Target: "user:sara",
})
if err != nil {
  log.Fatal("Error rejecting follow request:", err)
}

Push Preferences on Follow

When following a feed, you can set push_preference to control push notifications for future activities from that feed:

  • all - Receive push notifications for all activities from the followed feed
  • none (default) - Don’t receive push notifications for activities from the followed feed

Note: The push_preference parameter controls future notifications from the followed feed, while skip_push controls whether the follow action itself triggers a notification.

Examples: Push Preferences vs Skip Push

Understanding the difference between push_preference and skip_push:

// Scenario 1: Follow a user and receive notifications for their future activities
await timeline.follow("user:alice", {
  push_preference: "all", // You'll get push notifications for Alice's future posts
});

// Scenario 2: Follow a user but don't get notifications for their activities
await timeline.follow("user:bob", {
  push_preference: "none", // You won't get push notifications for Bob's future posts
});

// Scenario 3: Follow a user silently
await timeline.follow("user:charlie", {
  skip_push: true, // Charlie won't get a "you have a new follower" notification
  push_preference: "all", // But you'll still get notifications for Charlie's future posts
});

// Scenario 4: Silent follow with no future notifications
await timeline.follow("user:diana", {
  skip_push: true, // Diana won't know you followed her
  push_preference: "none", // And you won't get notifications for her posts
});

Follow Suggestions

Stream provides intelligent follow suggestions to help users discover feeds they might want to follow based on their activity and social graph.

// Get follow suggestions for a user
suggestions, err := client.Feeds().GetFollowSuggestions(context.Background(), &getstream.GetFollowSuggestionsRequest{
    FeedGroupId: "user",
    Limit:       getstream.PtrTo(10),
    UserId:      getstream.PtrTo("john"),
})
if err != nil {
    log.Fatal("Error getting follow suggestions:", err)
}

fmt.Printf("Algorithm used: %s\n", suggestions.Data.AlgorithmUsed)
fmt.Printf("Duration: %s\n", suggestions.Data.Duration)

for _, suggestion := range suggestions.Data.Suggestions {
    fmt.Printf("Suggested feed: %s\n", suggestion.Fid)
    fmt.Printf("Name: %s\n", suggestion.Name)
    fmt.Printf("Description: %s\n", suggestion.Description)
    fmt.Printf("Follower count: %d\n", suggestion.FollowerCount)
    fmt.Printf("Recommendation score: %.2f\n", suggestion.RecommendationScore)
    fmt.Printf("Reason: %s\n", suggestion.Reason)
    fmt.Printf("Algorithm scores: %+v\n", suggestion.AlgorithmScores)
}

Response Fields

The follow suggestions response includes:

  • suggestions: Array of suggested feeds to follow
    • feed: Feed identifier
    • name: Feed name
    • description: Feed description
    • visibility: Feed visibility setting
    • member_count: Number of members
    • follower_count: Number of followers
    • following_count: Number of feeds this feed follows
    • created_at: When the feed was created
    • updated_at: When the feed was last updated
    • recommendation_score: Combined recommendation score (0-1)
    • reason: Human-readable reason for the suggestion
    • algorithm_scores: Individual algorithm scores
  • algorithm_used: The algorithm used to generate suggestions
  • duration: Request processing time

Algorithm Types

Stream’s follow suggestions use a sophisticated multi-algorithm approach with weighted scoring:

  • popularity (Weight: 0.3): Based on follower count and engagement

    • Calculates normalized follower count relative to the most popular feed in your app
    • Score = min(follower_count / max_follower_count, 1.0)
    • Helps surface trending and popular content
  • friend-of-friend (Weight: 0.7): Based on social connections and mutual follows

    • Analyzes how many of your followed feeds also follow the suggested feed
    • Score = mutual_follows / your_total_follows
    • Leverages social proof and network effects
  • combined: Uses multiple algorithms with weighted scoring

    • Final score = (popularity_score × 0.3 + friend_of_friend_score × 0.7) / total_weight
    • Provides balanced recommendations combining popularity and social relevance

Note: Additional algorithms will be added in future releases to provide even more sophisticated recommendations.

Scoring System

The recommendation system uses a sophisticated scoring mechanism:

  1. Individual Algorithm Scores: Each algorithm calculates a score from 0.0 to 1.0
  2. Weighted Combination: Scores are combined using configurable weights
  3. Normalization: Final scores are normalized to ensure fair comparison
  4. Filtering: Only feeds with positive combined scores are included
  5. Ranking: Results are sorted by combined score in descending order

Features

  • Excludes feeds already followed by the user
  • Excludes user’s own feeds
  • Sophisticated algorithm to find feeds to follow
© Getstream.io, Inc. All Rights Reserved.