gtsocial-umbx

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

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 }