public_timeline.go (3938B)
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 "time" 24 25 "github.com/superseriousbusiness/gotosocial/internal/cache" 26 "github.com/superseriousbusiness/gotosocial/internal/gtscontext" 27 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 28 "github.com/superseriousbusiness/gotosocial/internal/log" 29 ) 30 31 // StatusHomeTimelineable checks if given status should be included on requester's public timeline. Primarily relying on status visibility to requester and the AP visibility setting, and ignoring conversation threads. 32 func (f *Filter) StatusPublicTimelineable(ctx context.Context, requester *gtsmodel.Account, status *gtsmodel.Status) (bool, error) { 33 const vtype = cache.VisibilityTypePublic 34 35 // By default we assume no auth. 36 requesterID := noauth 37 38 if requester != nil { 39 // Use provided account ID. 40 requesterID = requester.ID 41 } 42 43 visibility, err := f.state.Caches.Visibility.Load("Type.RequesterID.ItemID", func() (*cache.CachedVisibility, error) { 44 // Visibility not yet cached, perform timeline visibility lookup. 45 visible, err := f.isStatusPublicTimelineable(ctx, requester, status) 46 if err != nil { 47 return nil, err 48 } 49 50 // Return visibility value. 51 return &cache.CachedVisibility{ 52 ItemID: status.ID, 53 RequesterID: requesterID, 54 Type: vtype, 55 Value: visible, 56 }, nil 57 }, vtype, requesterID, status.ID) 58 if err != nil { 59 if err == cache.SentinelError { 60 // Filter-out our temporary 61 // race-condition error. 62 return false, nil 63 } 64 65 return false, err 66 } 67 68 return visibility.Value, nil 69 } 70 71 func (f *Filter) isStatusPublicTimelineable(ctx context.Context, requester *gtsmodel.Account, status *gtsmodel.Status) (bool, error) { 72 if status.CreatedAt.After(time.Now().Add(24 * time.Hour)) { 73 // Statuses made over 1 day in the future we don't show... 74 log.Warnf(ctx, "status >24hrs in the future: %+v", status) 75 return false, nil 76 } 77 78 // Don't show boosts on timeline. 79 if status.BoostOfID != "" { 80 return false, nil 81 } 82 83 // Check whether status is visible to requesting account. 84 visible, err := f.StatusVisible(ctx, requester, status) 85 if err != nil { 86 return false, err 87 } 88 89 if !visible { 90 log.Trace(ctx, "status not visible to timeline requester") 91 return false, nil 92 } 93 94 for parent := status; parent.InReplyToURI != ""; { 95 // Fetch next parent to lookup. 96 parentID := parent.InReplyToID 97 if parentID == "" { 98 log.Warnf(ctx, "status not yet deref'd: %s", parent.InReplyToURI) 99 return false, cache.SentinelError 100 } 101 102 // Get the next parent in the chain from DB. 103 parent, err = f.state.DB.GetStatusByID( 104 gtscontext.SetBarebones(ctx), 105 parentID, 106 ) 107 if err != nil { 108 return false, fmt.Errorf("isStatusPublicTimelineable: error getting status parent %s: %w", parentID, err) 109 } 110 111 if parent.AccountID != status.AccountID { 112 // This is not a single author reply-chain-thread, 113 // instead is an actualy conversation. Don't timeline. 114 log.Trace(ctx, "ignoring multi-author reply-chain") 115 return false, nil 116 } 117 } 118 119 // This is either a visible status in a 120 // single-author thread, or a visible top 121 // level status. Show on public timeline. 122 return true, nil 123 }