diff --git a/internal/filter/visibility/status.go b/internal/filter/visibility/status.go index be59e800e..a0f971464 100644 --- a/internal/filter/visibility/status.go +++ b/internal/filter/visibility/status.go @@ -104,18 +104,20 @@ func (f *Filter) isStatusVisible( return false, nil } - if util.PtrOrValue(status.PendingApproval, false) { + if util.PtrOrZero(status.PendingApproval) { // Use a different visibility heuristic // for pending approval statuses. - return f.isPendingStatusVisible(ctx, + return isPendingStatusVisible( requester, status, - ) + ), nil } if requester == nil { // Use a different visibility // heuristic for unauthed requests. - return f.isStatusVisibleUnauthed(ctx, status) + return f.isStatusVisibleUnauthed( + ctx, status, + ) } /* @@ -210,45 +212,42 @@ func (f *Filter) isStatusVisible( } } -func (f *Filter) isPendingStatusVisible( - _ context.Context, - requester *gtsmodel.Account, - status *gtsmodel.Status, -) (bool, error) { +// isPendingStatusVisible returns whether a status pending approval is visible to requester. +func isPendingStatusVisible(requester *gtsmodel.Account, status *gtsmodel.Status) bool { if requester == nil { // Any old tom, dick, and harry can't // see pending-approval statuses, // no matter what their visibility. - return false, nil + return false } if status.AccountID == requester.ID { // This is requester's status, // so they can always see it. - return true, nil + return true } if status.InReplyToAccountID == requester.ID { // This status replies to requester, // so they can always see it (else // they can't approve it). - return true, nil + return true } if status.BoostOfAccountID == requester.ID { // This status boosts requester, // so they can always see it. - return true, nil + return true } - // Nobody else can see this. - return false, nil + // Nobody else + // can see this. + return false } -func (f *Filter) isStatusVisibleUnauthed( - ctx context.Context, - status *gtsmodel.Status, -) (bool, error) { +// isStatusVisibleUnauthed returns whether status is visible without any unauthenticated account. +func (f *Filter) isStatusVisibleUnauthed(ctx context.Context, status *gtsmodel.Status) (bool, error) { + // For remote accounts, only show // Public statuses via the web. if status.Account.IsRemote() { @@ -275,8 +274,7 @@ func (f *Filter) isStatusVisibleUnauthed( } } - webVisibility := status.Account.Settings.WebVisibility - switch webVisibility { + switch webvis := status.Account.Settings.WebVisibility; webvis { // public_only: status must be Public. case gtsmodel.VisibilityPublic: @@ -296,7 +294,7 @@ func (f *Filter) isStatusVisibleUnauthed( default: return false, gtserror.Newf( "unrecognized web visibility for account %s: %s", - status.Account.ID, webVisibility, + status.Account.ID, webvis, ) } } diff --git a/internal/processing/common/status.go b/internal/processing/common/status.go index a1d432eb0..b10f9783d 100644 --- a/internal/processing/common/status.go +++ b/internal/processing/common/status.go @@ -25,6 +25,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/federation/dereferencing" statusfilter "github.com/superseriousbusiness/gotosocial/internal/filter/status" + "github.com/superseriousbusiness/gotosocial/internal/filter/usermute" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/log" @@ -174,14 +175,72 @@ func (p *Processor) GetAPIStatus( apiStatus *apimodel.Status, errWithCode gtserror.WithCode, ) { - apiStatus, err := p.converter.StatusToAPIStatus(ctx, target, requester, statusfilter.FilterContextNone, nil, nil) + apiStatus, err := p.converter.StatusToAPIStatus(ctx, + target, + requester, + statusfilter.FilterContextNone, + nil, + nil, + ) if err != nil { - err = gtserror.Newf("error converting status: %w", err) + err := gtserror.Newf("error converting status: %w", err) return nil, gtserror.NewErrorInternalError(err) } return apiStatus, nil } +// GetVisibleAPIStatuses converts a slice of statuses to API +// model statuses, filtering according to visibility to requester +// along with given filter context, filters and user mutes. +func (p *Processor) GetVisibleAPIStatuses( + ctx context.Context, + requester *gtsmodel.Account, + statuses []*gtsmodel.Status, + filterContext statusfilter.FilterContext, + filters []*gtsmodel.Filter, + userMutes []*gtsmodel.UserMute, +) []apimodel.Status { + // Compile mutes to useable user mutes for type converter. + compUserMutes := usermute.NewCompiledUserMuteList(userMutes) + + // Iterate filtered statuses for conversion to API model. + apiStatuses := make([]apimodel.Status, 0, len(statuses)) + for _, status := range statuses { + + // Check whether status is visible to requester. + visible, err := p.visFilter.StatusVisible(ctx, + requester, + status, + ) + if err != nil { + log.Errorf(ctx, "error checking status visibility: %v", err) + continue + } + + if !visible { + continue + } + + // Convert to API status, taking mute / filter into account. + apiStatus, err := p.converter.StatusToAPIStatus(ctx, + status, + requester, + filterContext, + filters, + compUserMutes, + ) + if err != nil && !errors.Is(err, statusfilter.ErrHideStatus) { + log.Errorf(ctx, "error converting to api model: %v", err) + continue + } + + // Append converted status to return slice. + apiStatuses = append(apiStatuses, *apiStatus) + } + + return apiStatuses +} + // InvalidateTimelinedStatus is a shortcut function for invalidating the cached // representation one status in the home timeline and all list timelines of the // given accountID. It should only be called in cases where a status update diff --git a/internal/processing/status/context.go b/internal/processing/status/context.go index 9f3a7d089..19c6cac18 100644 --- a/internal/processing/status/context.go +++ b/internal/processing/status/context.go @@ -24,7 +24,6 @@ import ( apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" statusfilter "github.com/superseriousbusiness/gotosocial/internal/filter/status" - "github.com/superseriousbusiness/gotosocial/internal/filter/usermute" "github.com/superseriousbusiness/gotosocial/internal/gtscontext" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" @@ -308,22 +307,7 @@ func (p *Processor) ContextGet( return nil, gtserror.NewErrorInternalError(err) } - convert := func( - ctx context.Context, - status *gtsmodel.Status, - requestingAccount *gtsmodel.Account, - ) (*apimodel.Status, error) { - return p.converter.StatusToAPIStatus( - ctx, - status, - requestingAccount, - statusfilter.FilterContextThread, - filters, - usermute.NewCompiledUserMuteList(mutes), - ) - } - - // Retrieve the thread context. + // Retrieve the full thread context. threadContext, errWithCode := p.contextGet( ctx, requester, @@ -333,34 +317,27 @@ func (p *Processor) ContextGet( return nil, errWithCode } - apiContext := &apimodel.ThreadContext{ - Ancestors: make([]apimodel.Status, 0, len(threadContext.ancestors)), - Descendants: make([]apimodel.Status, 0, len(threadContext.descendants)), - } + var apiContext apimodel.ThreadContext - // Convert ancestors + filter - // out ones that aren't visible. - for _, status := range threadContext.ancestors { - if v, err := p.visFilter.StatusVisible(ctx, requester, status); err == nil && v { - status, err := convert(ctx, status, requester) - if err == nil { - apiContext.Ancestors = append(apiContext.Ancestors, *status) - } - } - } + // Convert and filter the thread context ancestors. + apiContext.Ancestors = p.c.GetVisibleAPIStatuses(ctx, + requester, + threadContext.ancestors, + statusfilter.FilterContextThread, + filters, + mutes, + ) - // Convert descendants + filter - // out ones that aren't visible. - for _, status := range threadContext.descendants { - if v, err := p.visFilter.StatusVisible(ctx, requester, status); err == nil && v { - status, err := convert(ctx, status, requester) - if err == nil { - apiContext.Descendants = append(apiContext.Descendants, *status) - } - } - } + // Convert and filter the thread context descendants + apiContext.Descendants = p.c.GetVisibleAPIStatuses(ctx, + requester, + threadContext.descendants, + statusfilter.FilterContextThread, + filters, + mutes, + ) - return apiContext, nil + return &apiContext, nil } // WebContextGet is like ContextGet, but is explicitly