| 
									
										
										
										
											2023-03-12 16:00:57 +01:00
										 |  |  | // GoToSocial | 
					
						
							|  |  |  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | // GNU Affero General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | package fedi | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 	"net/url" | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	"slices" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	"github.com/superseriousbusiness/activity/streams/vocab" | 
					
						
							| 
									
										
										
										
											2023-05-09 12:16:10 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/paging" | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-07 14:58:53 +02:00
										 |  |  | // StatusGet handles the getting of a fedi/activitypub representation of a local status. | 
					
						
							|  |  |  | // It performs appropriate authentication before returning a JSON serializable interface. | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | func (p *Processor) StatusGet(ctx context.Context, requestedUser string, statusID string) (interface{}, gtserror.WithCode) { | 
					
						
							|  |  |  | 	// Authenticate the incoming request, getting related user accounts. | 
					
						
							|  |  |  | 	requester, receiver, errWithCode := p.authenticate(ctx, requestedUser) | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 	if errWithCode != nil { | 
					
						
							|  |  |  | 		return nil, errWithCode | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	status, err := p.state.DB.GetStatusByID(ctx, statusID) | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-03-01 18:52:44 +01:00
										 |  |  | 		return nil, gtserror.NewErrorNotFound(err) | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	if status.AccountID != receiver.ID { | 
					
						
							|  |  |  | 		const text = "status does not belong to receiving account" | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(errors.New(text)) | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-01 15:27:15 +01:00
										 |  |  | 	if status.BoostOfID != "" { | 
					
						
							|  |  |  | 		const text = "status is a boost wrapper" | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(errors.New(text)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	visible, err := p.filter.StatusVisible(ctx, requester, status) | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-07-07 14:58:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 	if !visible { | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		const text = "status not vising to requesting account" | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(errors.New(text)) | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-08 14:32:17 +00:00
										 |  |  | 	statusable, err := p.converter.StatusToAS(ctx, status) | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		err := gtserror.Newf("error converting status: %w", err) | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-08 14:32:17 +00:00
										 |  |  | 	data, err := ap.Serialize(statusable) | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		err := gtserror.Newf("error serializing status: %w", err) | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return data, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | // GetStatus handles the getting of a fedi/activitypub representation of replies to a status, | 
					
						
							|  |  |  | // performing appropriate authentication before returning a JSON serializable interface to the caller. | 
					
						
							|  |  |  | func (p *Processor) StatusRepliesGet( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	requestedUser string, | 
					
						
							|  |  |  | 	statusID string, | 
					
						
							|  |  |  | 	page *paging.Page, | 
					
						
							|  |  |  | 	onlyOtherAccounts bool, | 
					
						
							|  |  |  | ) (interface{}, gtserror.WithCode) { | 
					
						
							|  |  |  | 	// Authenticate the incoming request, getting related user accounts. | 
					
						
							|  |  |  | 	requester, receiver, errWithCode := p.authenticate(ctx, requestedUser) | 
					
						
							|  |  |  | 	if errWithCode != nil { | 
					
						
							|  |  |  | 		return nil, errWithCode | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get target status and ensure visible to requester. | 
					
						
							|  |  |  | 	status, errWithCode := p.c.GetVisibleTargetStatus(ctx, | 
					
						
							|  |  |  | 		requester, | 
					
						
							|  |  |  | 		statusID, | 
					
						
							| 
									
										
										
										
											2024-02-09 15:24:49 +01:00
										 |  |  | 		nil, // default freshness | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	) | 
					
						
							| 
									
										
										
										
											2022-04-26 18:10:11 +02:00
										 |  |  | 	if errWithCode != nil { | 
					
						
							|  |  |  | 		return nil, errWithCode | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	// Ensure status is by receiving account. | 
					
						
							|  |  |  | 	if status.AccountID != receiver.ID { | 
					
						
							|  |  |  | 		const text = "status does not belong to receiving account" | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(errors.New(text)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-01 15:27:15 +01:00
										 |  |  | 	if status.BoostOfID != "" { | 
					
						
							|  |  |  | 		const text = "status is a boost wrapper" | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(errors.New(text)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	// Parse replies collection ID from status' URI with onlyOtherAccounts param. | 
					
						
							|  |  |  | 	onlyOtherAccStr := "only_other_accounts=" + strconv.FormatBool(onlyOtherAccounts) | 
					
						
							|  |  |  | 	collectionID, err := url.Parse(status.URI + "/replies?" + onlyOtherAccStr) | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		err := gtserror.Newf("error parsing status uri %s: %w", status.URI, err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	// Get *all* available replies for status (i.e. without paging). | 
					
						
							|  |  |  | 	replies, err := p.state.DB.GetStatusReplies(ctx, status.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err := gtserror.Newf("error getting status replies: %w", err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	if onlyOtherAccounts { | 
					
						
							|  |  |  | 		// If 'onlyOtherAccounts' is set, drop all by original status author. | 
					
						
							|  |  |  | 		replies = slices.DeleteFunc(replies, func(reply *gtsmodel.Status) bool { | 
					
						
							|  |  |  | 			return reply.AccountID == status.AccountID | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Reslice replies dropping all those invisible to requester. | 
					
						
							|  |  |  | 	replies, err = p.filter.StatusesVisible(ctx, requester, replies) | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		err := gtserror.Newf("error filtering status replies: %w", err) | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	var obj vocab.Type | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Start AS collection params. | 
					
						
							|  |  |  | 	var params ap.CollectionParams | 
					
						
							|  |  |  | 	params.ID = collectionID | 
					
						
							|  |  |  | 	params.Total = len(replies) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if page == nil { | 
					
						
							|  |  |  | 		// i.e. paging disabled, return collection | 
					
						
							|  |  |  | 		// that links to first page (i.e. path below). | 
					
						
							|  |  |  | 		params.Query = make(url.Values, 1) | 
					
						
							|  |  |  | 		params.Query.Set("limit", "20") // enables paging | 
					
						
							|  |  |  | 		obj = ap.NewASOrderedCollection(params) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// i.e. paging enabled | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Page and reslice the replies according to given parameters. | 
					
						
							|  |  |  | 		replies = paging.Page_PageFunc(page, replies, func(reply *gtsmodel.Status) string { | 
					
						
							|  |  |  | 			return reply.ID | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// page ID values. | 
					
						
							|  |  |  | 		var lo, hi string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if len(replies) > 0 { | 
					
						
							|  |  |  | 			// Get the lowest and highest | 
					
						
							|  |  |  | 			// ID values, used for paging. | 
					
						
							|  |  |  | 			lo = replies[len(replies)-1].ID | 
					
						
							|  |  |  | 			hi = replies[0].ID | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		// Start AS collection page params. | 
					
						
							|  |  |  | 		var pageParams ap.CollectionPageParams | 
					
						
							|  |  |  | 		pageParams.CollectionParams = params | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		// Current page details. | 
					
						
							|  |  |  | 		pageParams.Current = page | 
					
						
							|  |  |  | 		pageParams.Count = len(replies) | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		// Set linked next/prev parameters. | 
					
						
							|  |  |  | 		pageParams.Next = page.Next(lo, hi) | 
					
						
							|  |  |  | 		pageParams.Prev = page.Prev(lo, hi) | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		// Set the collection item property builder function. | 
					
						
							|  |  |  | 		pageParams.Append = func(i int, itemsProp ap.ItemsPropertyBuilder) { | 
					
						
							|  |  |  | 			// Get follower URI at index. | 
					
						
							|  |  |  | 			status := replies[i] | 
					
						
							|  |  |  | 			uri := status.URI | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 			// Parse URL object from URI. | 
					
						
							|  |  |  | 			iri, err := url.Parse(uri) | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 				log.Errorf(ctx, "error parsing status uri %s: %v", uri, err) | 
					
						
							|  |  |  | 				return | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 			// Add to item property. | 
					
						
							|  |  |  | 			itemsProp.AppendIRI(iri) | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		// Build AS collection page object from params. | 
					
						
							|  |  |  | 		obj = ap.NewASOrderedCollectionPage(pageParams) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Serialized the prepared object. | 
					
						
							|  |  |  | 	data, err := ap.Serialize(obj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err := gtserror.Newf("error serializing: %w", err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							| 
									
										
										
										
											2021-10-24 11:57:39 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return data, nil | 
					
						
							|  |  |  | } |