| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | /* | 
					
						
							|  |  |  |    GoToSocial | 
					
						
							| 
									
										
										
										
											2021-12-20 18:42:19 +01:00
										 |  |  |    Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |    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/>. | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package dereferencing | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/sirupsen/logrus" | 
					
						
							| 
									
										
										
										
											2021-11-13 17:29:43 +01:00
										 |  |  | 	"github.com/superseriousbusiness/activity/streams" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/activity/streams/vocab" | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/id" | 
					
						
							| 
									
										
										
										
											2022-01-09 18:41:22 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/media" | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // EnrichRemoteStatus takes a status that's already been inserted into the database in a minimal form, | 
					
						
							|  |  |  | // and populates it with additional fields, media, etc. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // EnrichRemoteStatus is mostly useful for calling after a status has been initially created by | 
					
						
							|  |  |  | // the federatingDB's Create function, but additional dereferencing is needed on it. | 
					
						
							| 
									
										
										
										
											2021-09-14 12:23:56 +02:00
										 |  |  | func (d *deref) EnrichRemoteStatus(ctx context.Context, username string, status *gtsmodel.Status, includeParent bool) (*gtsmodel.Status, error) { | 
					
						
							|  |  |  | 	if err := d.populateStatusFields(ctx, status, username, includeParent); err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-10 14:36:10 +02:00
										 |  |  | 	if err := d.db.UpdateByPrimaryKey(ctx, status); err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		return nil, fmt.Errorf("EnrichRemoteStatus: error updating status: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return status, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // GetRemoteStatus completely dereferences a remote status, converts it to a GtS model status, | 
					
						
							|  |  |  | // puts it in the database, and returns it to a caller. The boolean indicates whether the status is new | 
					
						
							|  |  |  | // to us or not. If we haven't seen the status before, bool will be true. If we have seen the status before, | 
					
						
							|  |  |  | // it will be false. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // If refresh is true, then even if we have the status in our database already, it will be dereferenced from its | 
					
						
							|  |  |  | // remote representation, as will its owner. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // If a dereference was performed, then the function also returns the ap.Statusable representation for further processing. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // SIDE EFFECTS: remote status will be stored in the database, and the remote status owner will also be stored. | 
					
						
							| 
									
										
										
										
											2021-09-14 12:23:56 +02:00
										 |  |  | func (d *deref) GetRemoteStatus(ctx context.Context, username string, remoteStatusID *url.URL, refresh, includeParent bool) (*gtsmodel.Status, ap.Statusable, bool, error) { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	new := true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// check if we already have the status in our db | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	maybeStatus, err := d.db.GetStatusByURI(ctx, remoteStatusID.String()) | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	if err == nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		// we've seen this status before so it's not new | 
					
						
							|  |  |  | 		new = false | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// if we're not being asked to refresh, we can just return the maybeStatus as-is and avoid doing any external calls | 
					
						
							|  |  |  | 		if !refresh { | 
					
						
							|  |  |  | 			return maybeStatus, nil, new, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	statusable, err := d.dereferenceStatusable(ctx, username, remoteStatusID) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error dereferencing statusable: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	accountURI, err := ap.ExtractAttributedTo(statusable) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error extracting attributedTo: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// do this so we know we have the remote account of the status in the db | 
					
						
							| 
									
										
										
										
											2022-01-25 13:48:13 +01:00
										 |  |  | 	_, err = d.GetRemoteAccount(ctx, username, accountURI, true, false) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, statusable, new, fmt.Errorf("GetRemoteStatus: couldn't derive status author: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	gtsStatus, err := d.typeConverter.ASStatusToStatus(ctx, statusable) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error converting statusable to status: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if new { | 
					
						
							|  |  |  | 		ulid, err := id.NewULIDFromTime(gtsStatus.CreatedAt) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error generating new id for status: %s", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		gtsStatus.ID = ulid | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-14 12:23:56 +02:00
										 |  |  | 		if err := d.populateStatusFields(ctx, gtsStatus, username, includeParent); err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 			return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error populating status fields: %s", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 		if err := d.db.PutStatus(ctx, gtsStatus); err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 			return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error putting new status: %s", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		gtsStatus.ID = maybeStatus.ID | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-14 12:23:56 +02:00
										 |  |  | 		if err := d.populateStatusFields(ctx, gtsStatus, username, includeParent); err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 			return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error populating status fields: %s", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-10 14:36:10 +02:00
										 |  |  | 		if err := d.db.UpdateByPrimaryKey(ctx, gtsStatus); err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 			return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error updating status: %s", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return gtsStatus, statusable, new, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | func (d *deref) dereferenceStatusable(ctx context.Context, username string, remoteStatusID *url.URL) (ap.Statusable, error) { | 
					
						
							|  |  |  | 	if blocked, err := d.db.IsDomainBlocked(ctx, remoteStatusID.Host); blocked || err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		return nil, fmt.Errorf("DereferenceStatusable: domain %s is blocked", remoteStatusID.Host) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	transport, err := d.transportController.NewTransportForUsername(ctx, username) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("DereferenceStatusable: transport err: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-04 15:24:19 +02:00
										 |  |  | 	b, err := transport.Dereference(ctx, remoteStatusID) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("DereferenceStatusable: error deferencing %s: %s", remoteStatusID.String(), err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	m := make(map[string]interface{}) | 
					
						
							|  |  |  | 	if err := json.Unmarshal(b, &m); err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("DereferenceStatusable: error unmarshalling bytes into json: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-04 15:24:19 +02:00
										 |  |  | 	t, err := streams.ToType(ctx, m) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("DereferenceStatusable: error resolving json into ap vocab type: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Article, Document, Image, Video, Note, Page, Event, Place, Mention, Profile | 
					
						
							|  |  |  | 	switch t.GetTypeName() { | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 	case ap.ObjectArticle: | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		p, ok := t.(vocab.ActivityStreamsArticle) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsArticle") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return p, nil | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 	case ap.ObjectDocument: | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		p, ok := t.(vocab.ActivityStreamsDocument) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsDocument") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return p, nil | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 	case ap.ObjectImage: | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		p, ok := t.(vocab.ActivityStreamsImage) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsImage") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return p, nil | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 	case ap.ObjectVideo: | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		p, ok := t.(vocab.ActivityStreamsVideo) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsVideo") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return p, nil | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 	case ap.ObjectNote: | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		p, ok := t.(vocab.ActivityStreamsNote) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsNote") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return p, nil | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 	case ap.ObjectPage: | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		p, ok := t.(vocab.ActivityStreamsPage) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsPage") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return p, nil | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 	case ap.ObjectEvent: | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		p, ok := t.(vocab.ActivityStreamsEvent) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsEvent") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return p, nil | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 	case ap.ObjectPlace: | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		p, ok := t.(vocab.ActivityStreamsPlace) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsPlace") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return p, nil | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 	case ap.ObjectProfile: | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		p, ok := t.(vocab.ActivityStreamsProfile) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsProfile") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return p, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil, fmt.Errorf("DereferenceStatusable: type name %s not supported", t.GetTypeName()) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // populateStatusFields fetches all the information we temporarily pinned to an incoming | 
					
						
							|  |  |  | // federated status, back in the federating db's Create function. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // When a status comes in from the federation API, there are certain fields that | 
					
						
							|  |  |  | // haven't been dereferenced yet, because we needed to provide a snappy synchronous | 
					
						
							|  |  |  | // response to the caller. By the time it reaches this function though, it's being | 
					
						
							|  |  |  | // processed asynchronously, so we have all the time in the world to fetch the various | 
					
						
							|  |  |  | // bits and bobs that are attached to the status, and properly flesh it out, before we | 
					
						
							|  |  |  | // send the status to any timelines and notify people. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Things to dereference and fetch here: | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // 1. Media attachments. | 
					
						
							|  |  |  | // 2. Hashtags. | 
					
						
							|  |  |  | // 3. Emojis. | 
					
						
							|  |  |  | // 4. Mentions. | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | // 5. Replied-to-status. | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | // | 
					
						
							|  |  |  | // SIDE EFFECTS: | 
					
						
							|  |  |  | // This function will deference all of the above, insert them in the database as necessary, | 
					
						
							|  |  |  | // and attach them to the status. The status itself will not be added to the database yet, | 
					
						
							|  |  |  | // that's up the caller to do. | 
					
						
							| 
									
										
										
										
											2021-09-14 12:23:56 +02:00
										 |  |  | func (d *deref) populateStatusFields(ctx context.Context, status *gtsmodel.Status, requestingUsername string, includeParent bool) error { | 
					
						
							| 
									
										
										
										
											2021-10-11 05:37:33 -07:00
										 |  |  | 	l := logrus.WithFields(logrus.Fields{ | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		"func":   "dereferenceStatusFields", | 
					
						
							|  |  |  | 		"status": fmt.Sprintf("%+v", status), | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	l.Debug("entering function") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 	statusIRI, err := url.Parse(status.URI) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 		return fmt.Errorf("populateStatusFields: couldn't parse status URI %s: %s", status.URI, err) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 	blocked, err := d.db.IsURIBlocked(ctx, statusIRI) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 		return fmt.Errorf("populateStatusFields: error checking blocked status of %s: %s", statusIRI, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if blocked { | 
					
						
							|  |  |  | 		return fmt.Errorf("populateStatusFields: domain %s is blocked", statusIRI) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// in case the status doesn't have an id yet (ie., it hasn't entered the database yet), then create one | 
					
						
							|  |  |  | 	if status.ID == "" { | 
					
						
							|  |  |  | 		newID, err := id.NewULIDFromTime(status.CreatedAt) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 			return fmt.Errorf("populateStatusFields: error creating ulid for status: %s", err) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		status.ID = newID | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 1. Media attachments. | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 	if err := d.populateStatusAttachments(ctx, status, requestingUsername); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("populateStatusFields: error populating status attachments: %s", err) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 2. Hashtags | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 	// TODO | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// 3. Emojis | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 	// TODO | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-14 12:23:56 +02:00
										 |  |  | 	// 4. Mentions | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 	// TODO: do we need to handle removing empty mention objects and just using mention IDs slice? | 
					
						
							| 
									
										
										
										
											2021-09-14 12:23:56 +02:00
										 |  |  | 	if err := d.populateStatusMentions(ctx, status, requestingUsername); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("populateStatusFields: error populating status mentions: %s", err) | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 	// 5. Replied-to-status (only if requested) | 
					
						
							|  |  |  | 	if includeParent { | 
					
						
							|  |  |  | 		if err := d.populateStatusRepliedTo(ctx, status, requestingUsername); err != nil { | 
					
						
							|  |  |  | 			return fmt.Errorf("populateStatusFields: error populating status repliedTo: %s", err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (d *deref) populateStatusMentions(ctx context.Context, status *gtsmodel.Status, requestingUsername string) error { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	// At this point, mentions should have the namestring and mentionedAccountURI set on them. | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 	// We can use these to find the accounts. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	mentionIDs := []string{} | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 	newMentions := []*gtsmodel.Mention{} | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	for _, m := range status.Mentions { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		if m.ID != "" { | 
					
						
							|  |  |  | 			// we've already populated this mention, since it has an ID | 
					
						
							| 
									
										
										
										
											2021-10-11 05:37:33 -07:00
										 |  |  | 			logrus.Debug("populateStatusMentions: mention already populated") | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 			mentionIDs = append(mentionIDs, m.ID) | 
					
						
							|  |  |  | 			newMentions = append(newMentions, m) | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		if m.TargetAccountURI == "" { | 
					
						
							| 
									
										
										
										
											2021-10-11 05:37:33 -07:00
										 |  |  | 			logrus.Debug("populateStatusMentions: target URI not set on mention") | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		targetAccountURI, err := url.Parse(m.TargetAccountURI) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2021-10-11 05:37:33 -07:00
										 |  |  | 			logrus.Debugf("populateStatusMentions: error parsing mentioned account uri %s: %s", m.TargetAccountURI, err) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		var targetAccount *gtsmodel.Account | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 		errs := []string{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// check if account is in the db already | 
					
						
							| 
									
										
										
										
											2021-09-14 12:23:56 +02:00
										 |  |  | 		if a, err := d.db.GetAccountByURI(ctx, targetAccountURI.String()); err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 			errs = append(errs, err.Error()) | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2021-10-11 05:37:33 -07:00
										 |  |  | 			logrus.Debugf("populateStatusMentions: got target account %s with id %s through GetAccountByURI", targetAccountURI, a.ID) | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 			targetAccount = a | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if targetAccount == nil { | 
					
						
							|  |  |  | 			// we didn't find the account in our database already | 
					
						
							|  |  |  | 			// check if we can get the account remotely (dereference it) | 
					
						
							| 
									
										
										
										
											2022-01-24 13:12:17 +01:00
										 |  |  | 			if a, err := d.GetRemoteAccount(ctx, requestingUsername, targetAccountURI, false, false); err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 				errs = append(errs, err.Error()) | 
					
						
							|  |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2021-10-11 05:37:33 -07:00
										 |  |  | 				logrus.Debugf("populateStatusMentions: got target account %s with id %s through GetRemoteAccount", targetAccountURI, a.ID) | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 				targetAccount = a | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if targetAccount == nil { | 
					
						
							| 
									
										
										
										
											2021-10-11 05:37:33 -07:00
										 |  |  | 			logrus.Debugf("populateStatusMentions: couldn't get target account %s: %s", m.TargetAccountURI, strings.Join(errs, " : ")) | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		mID, err := id.NewRandomULID() | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 			return fmt.Errorf("populateStatusMentions: error generating ulid: %s", err) | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 		newMention := >smodel.Mention{ | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 			ID:               mID, | 
					
						
							|  |  |  | 			StatusID:         status.ID, | 
					
						
							|  |  |  | 			Status:           m.Status, | 
					
						
							|  |  |  | 			CreatedAt:        status.CreatedAt, | 
					
						
							|  |  |  | 			UpdatedAt:        status.UpdatedAt, | 
					
						
							| 
									
										
										
										
											2021-09-14 12:23:56 +02:00
										 |  |  | 			OriginAccountID:  status.AccountID, | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 			OriginAccountURI: status.AccountURI, | 
					
						
							|  |  |  | 			OriginAccount:    status.Account, | 
					
						
							|  |  |  | 			TargetAccountID:  targetAccount.ID, | 
					
						
							|  |  |  | 			TargetAccount:    targetAccount, | 
					
						
							|  |  |  | 			NameString:       m.NameString, | 
					
						
							|  |  |  | 			TargetAccountURI: targetAccount.URI, | 
					
						
							|  |  |  | 			TargetAccountURL: targetAccount.URL, | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 		if err := d.db.Put(ctx, newMention); err != nil { | 
					
						
							|  |  |  | 			return fmt.Errorf("populateStatusMentions: error creating mention: %s", err) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		mentionIDs = append(mentionIDs, newMention.ID) | 
					
						
							|  |  |  | 		newMentions = append(newMentions, newMention) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	status.MentionIDs = mentionIDs | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 	status.Mentions = newMentions | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (d *deref) populateStatusAttachments(ctx context.Context, status *gtsmodel.Status, requestingUsername string) error { | 
					
						
							|  |  |  | 	// At this point we should know: | 
					
						
							|  |  |  | 	// * the media type of the file we're looking for (a.File.ContentType) | 
					
						
							|  |  |  | 	// * the file type (a.Type) | 
					
						
							|  |  |  | 	// * the remote URL (a.RemoteURL) | 
					
						
							|  |  |  | 	// This should be enough to dereference the piece of media. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	attachmentIDs := []string{} | 
					
						
							|  |  |  | 	attachments := []*gtsmodel.MediaAttachment{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, a := range status.Attachments { | 
					
						
							| 
									
										
										
										
											2021-09-04 14:02:01 +02:00
										 |  |  | 		a.AccountID = status.AccountID | 
					
						
							|  |  |  | 		a.StatusID = status.ID | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-24 13:12:17 +01:00
										 |  |  | 		processingMedia, err := d.GetRemoteMedia(ctx, requestingUsername, a.AccountID, a.RemoteURL, &media.AdditionalMediaInfo{ | 
					
						
							| 
									
										
										
										
											2022-01-09 18:41:22 +01:00
										 |  |  | 			CreatedAt:   &a.CreatedAt, | 
					
						
							|  |  |  | 			StatusID:    &a.StatusID, | 
					
						
							|  |  |  | 			RemoteURL:   &a.RemoteURL, | 
					
						
							|  |  |  | 			Description: &a.Description, | 
					
						
							|  |  |  | 			Blurhash:    &a.Blurhash, | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2022-01-08 17:17:01 +01:00
										 |  |  | 			logrus.Errorf("populateStatusAttachments: couldn't get remote media %s: %s", a.RemoteURL, err) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-24 13:12:17 +01:00
										 |  |  | 		attachment, err := processingMedia.LoadAttachment(ctx) | 
					
						
							| 
									
										
										
										
											2022-01-08 17:17:01 +01:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			logrus.Errorf("populateStatusAttachments: couldn't load remote attachment %s: %s", a.RemoteURL, err) | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		attachmentIDs = append(attachmentIDs, attachment.ID) | 
					
						
							|  |  |  | 		attachments = append(attachments, attachment) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	status.AttachmentIDs = attachmentIDs | 
					
						
							|  |  |  | 	status.Attachments = attachments | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (d *deref) populateStatusRepliedTo(ctx context.Context, status *gtsmodel.Status, requestingUsername string) error { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	if status.InReplyToURI != "" && status.InReplyToID == "" { | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		statusURI, err := url.Parse(status.InReplyToURI) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// see if we have the status in our db already | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 		replyToStatus, err := d.db.GetStatusByURI(ctx, status.InReplyToURI) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			// Status was not in the DB, try fetch | 
					
						
							| 
									
										
										
										
											2021-09-14 12:23:56 +02:00
										 |  |  | 			replyToStatus, _, _, err = d.GetRemoteStatus(ctx, requestingUsername, statusURI, false, false) | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return fmt.Errorf("populateStatusRepliedTo: couldn't get reply to status with uri %s: %s", status.InReplyToURI, err) | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// we have the status | 
					
						
							|  |  |  | 		status.InReplyToID = replyToStatus.ID | 
					
						
							|  |  |  | 		status.InReplyTo = replyToStatus | 
					
						
							|  |  |  | 		status.InReplyToAccountID = replyToStatus.AccountID | 
					
						
							|  |  |  | 		status.InReplyToAccount = replyToStatus.Account | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-08-29 12:03:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } |