status.go (6271B)
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 fedi 19 20 import ( 21 "context" 22 "fmt" 23 "net/url" 24 25 "github.com/superseriousbusiness/gotosocial/internal/ap" 26 "github.com/superseriousbusiness/gotosocial/internal/gtserror" 27 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 28 ) 29 30 // StatusGet handles the getting of a fedi/activitypub representation of a particular status, performing appropriate 31 // authentication before returning a JSON serializable interface to the caller. 32 func (p *Processor) StatusGet(ctx context.Context, requestedUsername string, requestedStatusID string) (interface{}, gtserror.WithCode) { 33 requestedAccount, requestingAccount, errWithCode := p.authenticate(ctx, requestedUsername) 34 if errWithCode != nil { 35 return nil, errWithCode 36 } 37 38 status, err := p.state.DB.GetStatusByID(ctx, requestedStatusID) 39 if err != nil { 40 return nil, gtserror.NewErrorNotFound(err) 41 } 42 43 if status.AccountID != requestedAccount.ID { 44 return nil, gtserror.NewErrorNotFound(fmt.Errorf("status with id %s does not belong to account with id %s", status.ID, requestedAccount.ID)) 45 } 46 47 visible, err := p.filter.StatusVisible(ctx, requestingAccount, status) 48 if err != nil { 49 return nil, gtserror.NewErrorInternalError(err) 50 } 51 if !visible { 52 return nil, gtserror.NewErrorNotFound(fmt.Errorf("status with id %s not visible to user with id %s", status.ID, requestingAccount.ID)) 53 } 54 55 asStatus, err := p.tc.StatusToAS(ctx, status) 56 if err != nil { 57 return nil, gtserror.NewErrorInternalError(err) 58 } 59 60 data, err := ap.Serialize(asStatus) 61 if err != nil { 62 return nil, gtserror.NewErrorInternalError(err) 63 } 64 65 return data, nil 66 } 67 68 // GetStatus handles the getting of a fedi/activitypub representation of replies to a status, performing appropriate 69 // authentication before returning a JSON serializable interface to the caller. 70 func (p *Processor) StatusRepliesGet(ctx context.Context, requestedUsername string, requestedStatusID string, page bool, onlyOtherAccounts bool, onlyOtherAccountsSet bool, minID string) (interface{}, gtserror.WithCode) { 71 requestedAccount, requestingAccount, errWithCode := p.authenticate(ctx, requestedUsername) 72 if errWithCode != nil { 73 return nil, errWithCode 74 } 75 76 status, err := p.state.DB.GetStatusByID(ctx, requestedStatusID) 77 if err != nil { 78 return nil, gtserror.NewErrorNotFound(err) 79 } 80 81 if status.AccountID != requestedAccount.ID { 82 return nil, gtserror.NewErrorNotFound(fmt.Errorf("status with id %s does not belong to account with id %s", status.ID, requestedAccount.ID)) 83 } 84 85 visible, err := p.filter.StatusVisible(ctx, requestedAccount, status) 86 if err != nil { 87 return nil, gtserror.NewErrorInternalError(err) 88 } 89 if !visible { 90 return nil, gtserror.NewErrorNotFound(fmt.Errorf("status with id %s not visible to user with id %s", status.ID, requestingAccount.ID)) 91 } 92 93 var data map[string]interface{} 94 95 // now there are three scenarios: 96 // 1. we're asked for the whole collection and not a page -- we can just return the collection, with no items, but a link to 'first' page. 97 // 2. we're asked for a page but only_other_accounts has not been set in the query -- so we should just return the first page of the collection, with no items. 98 // 3. we're asked for a page, and only_other_accounts has been set, and min_id has optionally been set -- so we need to return some actual items! 99 switch { 100 case !page: 101 // scenario 1 102 // get the collection 103 collection, err := p.tc.StatusToASRepliesCollection(ctx, status, onlyOtherAccounts) 104 if err != nil { 105 return nil, gtserror.NewErrorInternalError(err) 106 } 107 108 data, err = ap.Serialize(collection) 109 if err != nil { 110 return nil, gtserror.NewErrorInternalError(err) 111 } 112 case page && !onlyOtherAccountsSet: 113 // scenario 2 114 // get the collection 115 collection, err := p.tc.StatusToASRepliesCollection(ctx, status, onlyOtherAccounts) 116 if err != nil { 117 return nil, gtserror.NewErrorInternalError(err) 118 } 119 // but only return the first page 120 data, err = ap.Serialize(collection.GetActivityStreamsFirst().GetActivityStreamsCollectionPage()) 121 if err != nil { 122 return nil, gtserror.NewErrorInternalError(err) 123 } 124 default: 125 // scenario 3 126 // get immediate children 127 replies, err := p.state.DB.GetStatusChildren(ctx, status, true, minID) 128 if err != nil { 129 return nil, gtserror.NewErrorInternalError(err) 130 } 131 132 // filter children and extract URIs 133 replyURIs := map[string]*url.URL{} 134 for _, r := range replies { 135 // only show public or unlocked statuses as replies 136 if r.Visibility != gtsmodel.VisibilityPublic && r.Visibility != gtsmodel.VisibilityUnlocked { 137 continue 138 } 139 140 // respect onlyOtherAccounts parameter 141 if onlyOtherAccounts && r.AccountID == requestedAccount.ID { 142 continue 143 } 144 145 // only show replies that the status owner can see 146 visibleToStatusOwner, err := p.filter.StatusVisible(ctx, requestedAccount, r) 147 if err != nil || !visibleToStatusOwner { 148 continue 149 } 150 151 // only show replies that the requester can see 152 visibleToRequester, err := p.filter.StatusVisible(ctx, requestingAccount, r) 153 if err != nil || !visibleToRequester { 154 continue 155 } 156 157 rURI, err := url.Parse(r.URI) 158 if err != nil { 159 continue 160 } 161 162 replyURIs[r.ID] = rURI 163 } 164 165 repliesPage, err := p.tc.StatusURIsToASRepliesPage(ctx, status, onlyOtherAccounts, minID, replyURIs) 166 if err != nil { 167 return nil, gtserror.NewErrorInternalError(err) 168 } 169 data, err = ap.Serialize(repliesPage) 170 if err != nil { 171 return nil, gtserror.NewErrorInternalError(err) 172 } 173 } 174 175 return data, nil 176 }