statuses.go (6073B)
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 account 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 25 apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" 26 "github.com/superseriousbusiness/gotosocial/internal/db" 27 "github.com/superseriousbusiness/gotosocial/internal/gtserror" 28 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 29 "github.com/superseriousbusiness/gotosocial/internal/log" 30 "github.com/superseriousbusiness/gotosocial/internal/util" 31 ) 32 33 // StatusesGet fetches a number of statuses (in time descending order) from the 34 // target account, filtered by visibility according to the requesting account. 35 func (p *Processor) StatusesGet( 36 ctx context.Context, 37 requestingAccount *gtsmodel.Account, 38 targetAccountID string, 39 limit int, 40 excludeReplies bool, 41 excludeReblogs bool, 42 maxID string, 43 minID string, 44 pinned bool, 45 mediaOnly bool, 46 publicOnly bool, 47 ) (*apimodel.PageableResponse, gtserror.WithCode) { 48 if requestingAccount != nil { 49 blocked, err := p.state.DB.IsEitherBlocked(ctx, requestingAccount.ID, targetAccountID) 50 if err != nil { 51 return nil, gtserror.NewErrorInternalError(err) 52 } 53 54 if blocked { 55 err := errors.New("block exists between accounts") 56 return nil, gtserror.NewErrorNotFound(err) 57 } 58 } 59 60 var ( 61 statuses []*gtsmodel.Status 62 err error 63 ) 64 65 if pinned { 66 // Get *ONLY* pinned statuses. 67 statuses, err = p.state.DB.GetAccountPinnedStatuses(ctx, targetAccountID) 68 } else { 69 // Get account statuses which *may* include pinned ones. 70 statuses, err = p.state.DB.GetAccountStatuses(ctx, targetAccountID, limit, excludeReplies, excludeReblogs, maxID, minID, mediaOnly, publicOnly) 71 } 72 73 if err != nil && !errors.Is(err, db.ErrNoEntries) { 74 return nil, gtserror.NewErrorInternalError(err) 75 } 76 77 if len(statuses) == 0 { 78 return util.EmptyPageableResponse(), nil 79 } 80 81 // Filtering + serialization process is the same for 82 // both pinned status queries and 'normal' ones. 83 filtered, err := p.filter.StatusesVisible(ctx, requestingAccount, statuses) 84 if err != nil { 85 return nil, gtserror.NewErrorInternalError(err) 86 } 87 88 count := len(filtered) 89 if count == 0 { 90 // After filtering there were 91 // no statuses left to serve. 92 return util.EmptyPageableResponse(), nil 93 } 94 95 var ( 96 items = make([]interface{}, 0, count) 97 nextMaxIDValue string 98 prevMinIDValue string 99 ) 100 101 for i, s := range filtered { 102 // Set next + prev values before filtering and API 103 // converting, so caller can still page properly. 104 if i == count-1 { 105 nextMaxIDValue = s.ID 106 } 107 108 if i == 0 { 109 prevMinIDValue = s.ID 110 } 111 112 item, err := p.tc.StatusToAPIStatus(ctx, s, requestingAccount) 113 if err != nil { 114 log.Debugf(ctx, "skipping status %s because it couldn't be converted to its api representation: %s", s.ID, err) 115 continue 116 } 117 118 items = append(items, item) 119 } 120 121 if pinned { 122 // We don't page on pinned status responses, 123 // so we can save some work + just return items. 124 return &apimodel.PageableResponse{ 125 Items: items, 126 }, nil 127 } 128 129 return util.PackagePageableResponse(util.PageableResponseParams{ 130 Items: items, 131 Path: "/api/v1/accounts/" + targetAccountID + "/statuses", 132 NextMaxIDValue: nextMaxIDValue, 133 PrevMinIDValue: prevMinIDValue, 134 Limit: limit, 135 ExtraQueryParams: []string{ 136 fmt.Sprintf("exclude_replies=%t", excludeReplies), 137 fmt.Sprintf("exclude_reblogs=%t", excludeReblogs), 138 fmt.Sprintf("pinned=%t", pinned), 139 fmt.Sprintf("only_media=%t", mediaOnly), 140 fmt.Sprintf("only_public=%t", publicOnly), 141 }, 142 }) 143 } 144 145 // WebStatusesGet fetches a number of statuses (in descending order) 146 // from the given account. It selects only statuses which are suitable 147 // for showing on the public web profile of an account. 148 func (p *Processor) WebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.PageableResponse, gtserror.WithCode) { 149 account, err := p.state.DB.GetAccountByID(ctx, targetAccountID) 150 if err != nil { 151 if errors.Is(err, db.ErrNoEntries) { 152 err := fmt.Errorf("account %s not found in the db, not getting web statuses for it", targetAccountID) 153 return nil, gtserror.NewErrorNotFound(err) 154 } 155 return nil, gtserror.NewErrorInternalError(err) 156 } 157 158 if account.Domain != "" { 159 err := fmt.Errorf("account %s was not a local account, not getting web statuses for it", targetAccountID) 160 return nil, gtserror.NewErrorNotFound(err) 161 } 162 163 statuses, err := p.state.DB.GetAccountWebStatuses(ctx, targetAccountID, 10, maxID) 164 if err != nil && !errors.Is(err, db.ErrNoEntries) { 165 return nil, gtserror.NewErrorInternalError(err) 166 } 167 168 count := len(statuses) 169 if count == 0 { 170 return util.EmptyPageableResponse(), nil 171 } 172 173 var ( 174 items = make([]interface{}, 0, count) 175 nextMaxIDValue string 176 ) 177 178 for i, s := range statuses { 179 // Set next value before API converting, 180 // so caller can still page properly. 181 if i == count-1 { 182 nextMaxIDValue = s.ID 183 } 184 185 item, err := p.tc.StatusToAPIStatus(ctx, s, nil) 186 if err != nil { 187 log.Debugf(ctx, "skipping status %s because it couldn't be converted to its api representation: %s", s.ID, err) 188 continue 189 } 190 191 items = append(items, item) 192 } 193 194 return util.PackagePageableResponse(util.PageableResponseParams{ 195 Items: items, 196 Path: "/@" + account.Username, 197 NextMaxIDValue: nextMaxIDValue, 198 }) 199 }