| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | // GoToSocial | 
					
						
							|  |  |  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | // GNU Affero General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package timeline | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | 	"context" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2025-02-13 12:34:45 +00:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | 	"net/url" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/cache/timeline" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | 
					
						
							|  |  |  | 	statusfilter "github.com/superseriousbusiness/gotosocial/internal/filter/status" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/filter/usermute" | 
					
						
							| 
									
										
										
										
											2024-02-27 13:22:05 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/filter/visibility" | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							| 
									
										
										
										
											2025-03-23 14:18:24 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/id" | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/paging" | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/state" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 
					
						
							| 
									
										
										
										
											2025-02-13 12:34:45 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/util/xslices" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	// pre-prepared URL values to be passed in to | 
					
						
							|  |  |  | 	// paging response forms. The paging package always | 
					
						
							|  |  |  | 	// copies values before any modifications so it's | 
					
						
							|  |  |  | 	// safe to only use a single map variable for these. | 
					
						
							|  |  |  | 	localOnlyTrue  = url.Values{"local": {"true"}} | 
					
						
							|  |  |  | 	localOnlyFalse = url.Values{"local": {"false"}} | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Processor struct { | 
					
						
							| 
									
										
										
										
											2023-09-23 17:44:11 +01:00
										 |  |  | 	state     *state.State | 
					
						
							|  |  |  | 	converter *typeutils.Converter | 
					
						
							| 
									
										
										
										
											2024-07-24 13:27:42 +02:00
										 |  |  | 	visFilter *visibility.Filter | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-24 13:27:42 +02:00
										 |  |  | func New(state *state.State, converter *typeutils.Converter, visFilter *visibility.Filter) Processor { | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | 	return Processor{ | 
					
						
							| 
									
										
										
										
											2023-09-23 17:44:11 +01:00
										 |  |  | 		state:     state, | 
					
						
							|  |  |  | 		converter: converter, | 
					
						
							| 
									
										
										
										
											2024-07-24 13:27:42 +02:00
										 |  |  | 		visFilter: visFilter, | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | func (p *Processor) getStatusTimeline( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	requester *gtsmodel.Account, | 
					
						
							| 
									
										
										
										
											2025-04-03 13:51:47 +01:00
										 |  |  | 	cache *timeline.StatusTimeline, | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | 	page *paging.Page, | 
					
						
							| 
									
										
										
										
											2025-03-25 12:12:09 +00:00
										 |  |  | 	pagePath string, | 
					
						
							|  |  |  | 	pageQuery url.Values, | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | 	filterCtx statusfilter.FilterContext, | 
					
						
							|  |  |  | 	loadPage func(*paging.Page) (statuses []*gtsmodel.Status, err error), | 
					
						
							| 
									
										
										
										
											2025-04-02 17:25:33 +01:00
										 |  |  | 	filter func(*gtsmodel.Status) (bool, error), | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | ) ( | 
					
						
							|  |  |  | 	*apimodel.PageableResponse, | 
					
						
							|  |  |  | 	gtserror.WithCode, | 
					
						
							|  |  |  | ) { | 
					
						
							| 
									
										
										
										
											2025-04-03 13:51:47 +01:00
										 |  |  | 	var err error | 
					
						
							|  |  |  | 	var filters []*gtsmodel.Filter | 
					
						
							|  |  |  | 	var mutes *usermute.CompiledUserMuteList | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if requester != nil { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Fetch all filters relevant for requesting account. | 
					
						
							|  |  |  | 		filters, err = p.state.DB.GetFiltersForAccountID(ctx, | 
					
						
							|  |  |  | 			requester.ID, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 			err := gtserror.Newf("error getting account filters: %w", err) | 
					
						
							|  |  |  | 			return nil, gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Get a list of all account mutes for requester. | 
					
						
							|  |  |  | 		allMutes, err := p.state.DB.GetAccountMutes(ctx, | 
					
						
							|  |  |  | 			requester.ID, | 
					
						
							| 
									
										
										
										
											2025-04-01 14:32:36 +01:00
										 |  |  | 			nil, // i.e. all | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 			err := gtserror.Newf("error getting account mutes: %w", err) | 
					
						
							|  |  |  | 			return nil, gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Compile all account mutes to useable form. | 
					
						
							|  |  |  | 		mutes = usermute.NewCompiledUserMuteList(allMutes) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-23 14:18:24 +00:00
										 |  |  | 	// Ensure we have valid | 
					
						
							| 
									
										
										
										
											2025-03-24 21:34:36 +00:00
										 |  |  | 	// input paging cursor. | 
					
						
							| 
									
										
										
										
											2025-03-23 14:18:24 +00:00
										 |  |  | 	id.ValidatePage(page) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-03 13:51:47 +01:00
										 |  |  | 	// Returned models and page params. | 
					
						
							|  |  |  | 	var apiStatuses []*apimodel.Status | 
					
						
							|  |  |  | 	var lo, hi string | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-03 17:17:39 +01:00
										 |  |  | 	// Track whether a particular BoostOfID | 
					
						
							|  |  |  | 	// has already been boosted (i.e., seen) | 
					
						
							|  |  |  | 	// before in this paged request for statuses. | 
					
						
							|  |  |  | 	boosted := make(map[string]struct{}, 4) | 
					
						
							|  |  |  | 	alreadyBoosted := func(s *gtsmodel.Status) bool { | 
					
						
							|  |  |  | 		if s.BoostOfID != "" { | 
					
						
							|  |  |  | 			_, ok := boosted[s.BoostOfID] | 
					
						
							|  |  |  | 			if ok { | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			boosted[s.BoostOfID] = struct{}{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Pre-prepared filter function that just ensures we | 
					
						
							|  |  |  | 	// don't end up serving multiple copies of the same boost. | 
					
						
							|  |  |  | 	prepare := func(status *gtsmodel.Status) (*apimodel.Status, error) { | 
					
						
							|  |  |  | 		if alreadyBoosted(status) { | 
					
						
							|  |  |  | 			return nil, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		apiStatus, err := p.converter.StatusToAPIStatus(ctx, | 
					
						
							|  |  |  | 			status, | 
					
						
							|  |  |  | 			requester, | 
					
						
							|  |  |  | 			filterCtx, | 
					
						
							|  |  |  | 			filters, | 
					
						
							|  |  |  | 			mutes, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil && !errors.Is(err, statusfilter.ErrHideStatus) { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return apiStatus, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-03 13:51:47 +01:00
										 |  |  | 	if cache != nil { | 
					
						
							|  |  |  | 		// Load status page via timeline cache, also | 
					
						
							|  |  |  | 		// getting lo, hi values for next, prev pages. | 
					
						
							|  |  |  | 		apiStatuses, lo, hi, err = cache.Load(ctx, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Status page | 
					
						
							|  |  |  | 			// to load. | 
					
						
							|  |  |  | 			page, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Caller provided database | 
					
						
							|  |  |  | 			// status page loading function. | 
					
						
							|  |  |  | 			loadPage, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Status load function for cached timeline entries. | 
					
						
							|  |  |  | 			func(ids []string) ([]*gtsmodel.Status, error) { | 
					
						
							|  |  |  | 				return p.state.DB.GetStatusesByIDs(ctx, ids) | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Filtering function, | 
					
						
							|  |  |  | 			// i.e. filter before caching. | 
					
						
							|  |  |  | 			filter, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-03 17:17:39 +01:00
										 |  |  | 			// Frontend API model | 
					
						
							|  |  |  | 			// preparation function. | 
					
						
							|  |  |  | 			prepare, | 
					
						
							| 
									
										
										
										
											2025-04-03 13:51:47 +01:00
										 |  |  | 		) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// Load status page without a receiving timeline cache. | 
					
						
							|  |  |  | 		// TODO: remove this code path when all support caching. | 
					
						
							|  |  |  | 		apiStatuses, lo, hi, err = timeline.LoadStatusTimeline(ctx, | 
					
						
							|  |  |  | 			page, | 
					
						
							|  |  |  | 			loadPage, | 
					
						
							|  |  |  | 			func(ids []string) ([]*gtsmodel.Status, error) { | 
					
						
							|  |  |  | 				return p.state.DB.GetStatusesByIDs(ctx, ids) | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			filter, | 
					
						
							| 
									
										
										
										
											2025-04-03 17:17:39 +01:00
										 |  |  | 			prepare, | 
					
						
							| 
									
										
										
										
											2025-04-03 13:51:47 +01:00
										 |  |  | 		) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2025-02-13 12:34:45 +00:00
										 |  |  | 		err := gtserror.Newf("error loading timeline: %w", err) | 
					
						
							|  |  |  | 		return nil, gtserror.WrapWithCode(http.StatusInternalServerError, err) | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-13 12:34:45 +00:00
										 |  |  | 	// Package returned API statuses as pageable response. | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | 	return paging.PackageResponse(paging.ResponseParams{ | 
					
						
							| 
									
										
										
										
											2025-02-13 12:34:45 +00:00
										 |  |  | 		Items: xslices.ToAny(apiStatuses), | 
					
						
							| 
									
										
										
										
											2025-03-25 12:12:09 +00:00
										 |  |  | 		Path:  pagePath, | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | 		Next:  page.Next(lo, hi), | 
					
						
							|  |  |  | 		Prev:  page.Prev(lo, hi), | 
					
						
							| 
									
										
										
										
											2025-03-25 12:12:09 +00:00
										 |  |  | 		Query: pageQuery, | 
					
						
							| 
									
										
										
										
											2024-12-30 17:12:55 +00:00
										 |  |  | 	}), nil | 
					
						
							|  |  |  | } |