mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-29 04:22:24 -05:00 
			
		
		
		
	[feature] Push status edit messages into open streams (#2418)
* push status edit messages into open streams * fix a few comments * test++ * commented out code? moi?
This commit is contained in:
		
					parent
					
						
							
								fbe4e60232
							
						
					
				
			
			
				commit
				
					
						285d55dda8
					
				
			
		
					 7 changed files with 363 additions and 0 deletions
				
			
		|  | @ -390,3 +390,176 @@ func (s *surface) invalidateStatusFromTimelines(ctx context.Context, statusID st | |||
| 			Errorf("error unpreparing status from list timelines: %v", err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // timelineStatusUpdate looks up HOME and LIST timelines of accounts | ||||
| // that follow the the status author and pushes edit messages into any | ||||
| // active streams. | ||||
| // Note that calling invalidateStatusFromTimelines takes care of the | ||||
| // state in general, we just need to do this for any streams that are | ||||
| // open right now. | ||||
| func (s *surface) timelineStatusUpdate(ctx context.Context, status *gtsmodel.Status) error { | ||||
| 	// Ensure status fully populated; including account, mentions, etc. | ||||
| 	if err := s.state.DB.PopulateStatus(ctx, status); err != nil { | ||||
| 		return gtserror.Newf("error populating status with id %s: %w", status.ID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Get all local followers of the account that posted the status. | ||||
| 	follows, err := s.state.DB.GetAccountLocalFollowers(ctx, status.AccountID) | ||||
| 	if err != nil { | ||||
| 		return gtserror.Newf("error getting local followers of account %s: %w", status.AccountID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	// If the poster is also local, add a fake entry for them | ||||
| 	// so they can see their own status in their timeline. | ||||
| 	if status.Account.IsLocal() { | ||||
| 		follows = append(follows, >smodel.Follow{ | ||||
| 			AccountID:   status.AccountID, | ||||
| 			Account:     status.Account, | ||||
| 			Notify:      func() *bool { b := false; return &b }(), // Account shouldn't notify itself. | ||||
| 			ShowReblogs: func() *bool { b := true; return &b }(),  // Account should show own reblogs. | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	// Push to streams for each local follower of this account. | ||||
| 	if err := s.timelineStatusUpdateForFollowers(ctx, status, follows); err != nil { | ||||
| 		return gtserror.Newf("error timelining status %s for followers: %w", status.ID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // timelineStatusUpdateForFollowers iterates through the given | ||||
| // slice of followers of the account that posted the given status, | ||||
| // pushing update messages into open list/home streams of each | ||||
| // follower. | ||||
| func (s *surface) timelineStatusUpdateForFollowers( | ||||
| 	ctx context.Context, | ||||
| 	status *gtsmodel.Status, | ||||
| 	follows []*gtsmodel.Follow, | ||||
| ) error { | ||||
| 	var ( | ||||
| 		errs gtserror.MultiError | ||||
| 	) | ||||
| 
 | ||||
| 	for _, follow := range follows { | ||||
| 		// Check to see if the status is timelineable for this follower, | ||||
| 		// taking account of its visibility, who it replies to, and, if | ||||
| 		// it's a reblog, whether follower account wants to see reblogs. | ||||
| 		// | ||||
| 		// If it's not timelineable, we can just stop early, since lists | ||||
| 		// are prettymuch subsets of the home timeline, so if it shouldn't | ||||
| 		// appear there, it shouldn't appear in lists either. | ||||
| 		timelineable, err := s.filter.StatusHomeTimelineable( | ||||
| 			ctx, follow.Account, status, | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			errs.Appendf("error checking status %s hometimelineability: %w", status.ID, err) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if !timelineable { | ||||
| 			// Nothing to do. | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// Add status to any relevant lists | ||||
| 		// for this follow, if applicable. | ||||
| 		s.listTimelineStatusUpdateForFollow( | ||||
| 			ctx, | ||||
| 			status, | ||||
| 			follow, | ||||
| 			&errs, | ||||
| 		) | ||||
| 
 | ||||
| 		// Add status to home timeline for owner | ||||
| 		// of this follow, if applicable. | ||||
| 		err = s.timelineStreamStatusUpdate( | ||||
| 			ctx, | ||||
| 			follow.Account, | ||||
| 			status, | ||||
| 			stream.TimelineHome, | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			errs.Appendf("error home timelining status: %w", err) | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return errs.Combine() | ||||
| } | ||||
| 
 | ||||
| // listTimelineStatusUpdateForFollow pushes edits of the given status | ||||
| // into any eligible lists streams opened by the given follower. | ||||
| func (s *surface) listTimelineStatusUpdateForFollow( | ||||
| 	ctx context.Context, | ||||
| 	status *gtsmodel.Status, | ||||
| 	follow *gtsmodel.Follow, | ||||
| 	errs *gtserror.MultiError, | ||||
| ) { | ||||
| 	// To put this status in appropriate list timelines, | ||||
| 	// we need to get each listEntry that pertains to | ||||
| 	// this follow. Then, we want to iterate through all | ||||
| 	// those list entries, and add the status to the list | ||||
| 	// that the entry belongs to if it meets criteria for | ||||
| 	// inclusion in the list. | ||||
| 
 | ||||
| 	// Get every list entry that targets this follow's ID. | ||||
| 	listEntries, err := s.state.DB.GetListEntriesForFollowID( | ||||
| 		// We only need the list IDs. | ||||
| 		gtscontext.SetBarebones(ctx), | ||||
| 		follow.ID, | ||||
| 	) | ||||
| 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | ||||
| 		errs.Appendf("error getting list entries: %w", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Check eligibility for each list entry (if any). | ||||
| 	for _, listEntry := range listEntries { | ||||
| 		eligible, err := s.listEligible(ctx, listEntry, status) | ||||
| 		if err != nil { | ||||
| 			errs.Appendf("error checking list eligibility: %w", err) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if !eligible { | ||||
| 			// Don't add this. | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// At this point we are certain this status | ||||
| 		// should be included in the timeline of the | ||||
| 		// list that this list entry belongs to. | ||||
| 		if err := s.timelineStreamStatusUpdate( | ||||
| 			ctx, | ||||
| 			follow.Account, | ||||
| 			status, | ||||
| 			stream.TimelineList+":"+listEntry.ListID, // key streamType to this specific list | ||||
| 		); err != nil { | ||||
| 			errs.Appendf("error adding status to timeline for list %s: %w", listEntry.ListID, err) | ||||
| 			// implicit continue | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // timelineStatusUpdate streams the edited status to the user using the | ||||
| // given streamType. | ||||
| func (s *surface) timelineStreamStatusUpdate( | ||||
| 	ctx context.Context, | ||||
| 	account *gtsmodel.Account, | ||||
| 	status *gtsmodel.Status, | ||||
| 	streamType string, | ||||
| ) error { | ||||
| 	apiStatus, err := s.converter.StatusToAPIStatus(ctx, status, account) | ||||
| 	if err != nil { | ||||
| 		err = gtserror.Newf("error converting status %s to frontend representation: %w", status.ID, err) | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if err := s.stream.StatusUpdate(apiStatus, account, []string{streamType}); err != nil { | ||||
| 		err = gtserror.Newf("error streaming update for status %s: %w", status.ID, err) | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue