gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

status.go (7181B)


      1 // GoToSocial
      2 // Copyright (C) GoToSocial Authors admin@gotosocial.org
      3 // SPDX-License-Identifier: AGPL-3.0-or-later
      4 //
      5 // This program is free software: you can redistribute it and/or modify
      6 // it under the terms of the GNU Affero General Public License as published by
      7 // the Free Software Foundation, either version 3 of the License, or
      8 // (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU Affero General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU Affero General Public License
     16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18 package visibility
     19 
     20 import (
     21 	"context"
     22 	"fmt"
     23 
     24 	"github.com/superseriousbusiness/gotosocial/internal/cache"
     25 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
     26 	"github.com/superseriousbusiness/gotosocial/internal/log"
     27 )
     28 
     29 // StatusesVisible calls StatusVisible for each status in the statuses slice, and returns a slice of only statuses which are visible to the requester.
     30 func (f *Filter) StatusesVisible(ctx context.Context, requester *gtsmodel.Account, statuses []*gtsmodel.Status) ([]*gtsmodel.Status, error) {
     31 	// Preallocate slice of maximum possible length.
     32 	filtered := make([]*gtsmodel.Status, 0, len(statuses))
     33 
     34 	for _, status := range statuses {
     35 		// Check whether status is visible to requester.
     36 		visible, err := f.StatusVisible(ctx, requester, status)
     37 		if err != nil {
     38 			return nil, err
     39 		}
     40 
     41 		if visible {
     42 			// Add filtered status to ret slice.
     43 			filtered = append(filtered, status)
     44 		}
     45 	}
     46 
     47 	return filtered, nil
     48 }
     49 
     50 // StatusVisible will check if given status is visible to requester, accounting for requester with no auth (i.e is nil), suspensions, disabled local users, account blocks and status privacy.
     51 func (f *Filter) StatusVisible(ctx context.Context, requester *gtsmodel.Account, status *gtsmodel.Status) (bool, error) {
     52 	const vtype = cache.VisibilityTypeStatus
     53 
     54 	// By default we assume no auth.
     55 	requesterID := noauth
     56 
     57 	if requester != nil {
     58 		// Use provided account ID.
     59 		requesterID = requester.ID
     60 	}
     61 
     62 	visibility, err := f.state.Caches.Visibility.Load("Type.RequesterID.ItemID", func() (*cache.CachedVisibility, error) {
     63 		// Visibility not yet cached, perform visibility lookup.
     64 		visible, err := f.isStatusVisible(ctx, requester, status)
     65 		if err != nil {
     66 			return nil, err
     67 		}
     68 
     69 		// Return visibility value.
     70 		return &cache.CachedVisibility{
     71 			ItemID:      status.ID,
     72 			RequesterID: requesterID,
     73 			Type:        vtype,
     74 			Value:       visible,
     75 		}, nil
     76 	}, vtype, requesterID, status.ID)
     77 	if err != nil {
     78 		return false, err
     79 	}
     80 
     81 	return visibility.Value, nil
     82 }
     83 
     84 // isStatusVisible will check if status is visible to requester. It is the "meat" of the logic to Filter{}.StatusVisible() which is called within cache loader callback.
     85 func (f *Filter) isStatusVisible(ctx context.Context, requester *gtsmodel.Account, status *gtsmodel.Status) (bool, error) {
     86 	// Ensure that status is fully populated for further processing.
     87 	if err := f.state.DB.PopulateStatus(ctx, status); err != nil {
     88 		return false, fmt.Errorf("isStatusVisible: error populating status %s: %w", status.ID, err)
     89 	}
     90 
     91 	// Check whether status accounts are visible to the requester.
     92 	visible, err := f.areStatusAccountsVisible(ctx, requester, status)
     93 	if err != nil {
     94 		return false, fmt.Errorf("isStatusVisible: error checking status %s account visibility: %w", status.ID, err)
     95 	} else if !visible {
     96 		return false, nil
     97 	}
     98 
     99 	if status.Visibility == gtsmodel.VisibilityPublic {
    100 		// This status will be visible to all.
    101 		return true, nil
    102 	}
    103 
    104 	if requester == nil {
    105 		// This request is WITHOUT auth, and status is NOT public.
    106 		log.Trace(ctx, "unauthorized request to non-public status")
    107 		return false, nil
    108 	}
    109 
    110 	if status.Visibility == gtsmodel.VisibilityUnlocked {
    111 		// This status is visible to all auth'd accounts.
    112 		return true, nil
    113 	}
    114 
    115 	if requester.ID == status.AccountID {
    116 		// Author can always see their own status.
    117 		return true, nil
    118 	}
    119 
    120 	if status.MentionsAccount(requester.ID) {
    121 		// Status mentions the requesting account.
    122 		return true, nil
    123 	}
    124 
    125 	if status.BoostOf != nil {
    126 		if !status.BoostOf.MentionsPopulated() {
    127 			// Boosted status needs its mentions populating, fetch these from database.
    128 			status.BoostOf.Mentions, err = f.state.DB.GetMentions(ctx, status.BoostOf.MentionIDs)
    129 			if err != nil {
    130 				return false, fmt.Errorf("isStatusVisible: error populating boosted status %s mentions: %w", status.BoostOfID, err)
    131 			}
    132 		}
    133 
    134 		if status.BoostOf.MentionsAccount(requester.ID) {
    135 			// Boosted status mentions the requesting account.
    136 			return true, nil
    137 		}
    138 	}
    139 
    140 	switch status.Visibility {
    141 	case gtsmodel.VisibilityFollowersOnly:
    142 		// Check requester follows status author.
    143 		follows, err := f.state.DB.IsFollowing(ctx,
    144 			requester.ID,
    145 			status.AccountID,
    146 		)
    147 		if err != nil {
    148 			return false, fmt.Errorf("isStatusVisible: error checking follow %s->%s: %w", requester.ID, status.AccountID, err)
    149 		}
    150 
    151 		if !follows {
    152 			log.Trace(ctx, "follow-only status not visible to requester")
    153 			return false, nil
    154 		}
    155 
    156 		return true, nil
    157 
    158 	case gtsmodel.VisibilityMutualsOnly:
    159 		// Check mutual following between requester and author.
    160 		mutuals, err := f.state.DB.IsMutualFollowing(ctx,
    161 			requester.ID,
    162 			status.AccountID,
    163 		)
    164 		if err != nil {
    165 			return false, fmt.Errorf("isStatusVisible: error checking mutual follow %s<->%s: %w", requester.ID, status.AccountID, err)
    166 		}
    167 
    168 		if !mutuals {
    169 			log.Trace(ctx, "mutual-only status not visible to requester")
    170 			return false, nil
    171 		}
    172 
    173 		return true, nil
    174 
    175 	case gtsmodel.VisibilityDirect:
    176 		log.Trace(ctx, "direct status not visible to requester")
    177 		return false, nil
    178 
    179 	default:
    180 		log.Warnf(ctx, "unexpected status visibility %s for %s", status.Visibility, status.URI)
    181 		return false, nil
    182 	}
    183 }
    184 
    185 // areStatusAccountsVisible calls Filter{}.AccountVisible() on status author and the status boost-of (if set) author, returning visibility of status (and boost-of) to requester.
    186 func (f *Filter) areStatusAccountsVisible(ctx context.Context, requester *gtsmodel.Account, status *gtsmodel.Status) (bool, error) {
    187 	// Check whether status author's account is visible to requester.
    188 	visible, err := f.AccountVisible(ctx, requester, status.Account)
    189 	if err != nil {
    190 		return false, fmt.Errorf("error checking status author visibility: %w", err)
    191 	}
    192 
    193 	if !visible {
    194 		log.Trace(ctx, "status author not visible to requester")
    195 		return false, nil
    196 	}
    197 
    198 	if status.BoostOfID != "" {
    199 		// This is a boosted status.
    200 
    201 		if status.AccountID == status.BoostOfAccountID {
    202 			// Some clout-chaser boosted their own status, tch.
    203 			return true, nil
    204 		}
    205 
    206 		// Check whether boosted status author's account is visible to requester.
    207 		visible, err := f.AccountVisible(ctx, requester, status.BoostOfAccount)
    208 		if err != nil {
    209 			return false, fmt.Errorf("error checking boosted author visibility: %w", err)
    210 		}
    211 
    212 		if !visible {
    213 			log.Trace(ctx, "boosted status author not visible to requester")
    214 			return false, nil
    215 		}
    216 	}
    217 
    218 	return true, nil
    219 }