| 
									
										
										
										
											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 ( | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/sirupsen/logrus" | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 	"github.com/spf13/viper" | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | 
					
						
							| 
									
										
										
										
											2021-12-20 15:19:53 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/uris" | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // DereferenceThread takes a statusable (something that has withReplies and withInReplyTo), | 
					
						
							|  |  |  | // and dereferences statusables in the conversation. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This process involves working up and down the chain of replies, and parsing through the collections of IDs | 
					
						
							|  |  |  | // presented by remote instances as part of their replies collections, and will likely involve making several calls to | 
					
						
							|  |  |  | // multiple different hosts. | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | func (d *deref) DereferenceThread(ctx context.Context, username string, statusIRI *url.URL) error { | 
					
						
							| 
									
										
										
										
											2021-10-11 05:37:33 -07:00
										 |  |  | 	l := logrus.WithFields(logrus.Fields{ | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		"func":      "DereferenceThread", | 
					
						
							|  |  |  | 		"username":  username, | 
					
						
							|  |  |  | 		"statusIRI": statusIRI.String(), | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	l.Debug("entering DereferenceThread") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// if it's our status we already have everything stashed so we can bail early | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 	host := viper.GetString(config.Keys.Host) | 
					
						
							|  |  |  | 	if statusIRI.Host == host { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		l.Debug("iri belongs to us, bailing") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// first make sure we have this status in our db | 
					
						
							| 
									
										
										
										
											2022-05-23 17:40:03 +02:00
										 |  |  | 	_, statusable, err := d.GetRemoteStatus(ctx, username, statusIRI, true, false) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-05-23 17:40:03 +02:00
										 |  |  | 		return fmt.Errorf("DereferenceThread: error getting initial status with id %s: %s", statusIRI.String(), err) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// first iterate up through ancestors, dereferencing if necessary as we go | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	if err := d.iterateAncestors(ctx, username, *statusIRI); err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		return fmt.Errorf("error iterating ancestors of status %s: %s", statusIRI.String(), err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// now iterate down through descendants, again dereferencing as we go | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	if err := d.iterateDescendants(ctx, username, *statusIRI, statusable); err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		return fmt.Errorf("error iterating descendants of status %s: %s", statusIRI.String(), err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // iterateAncestors has the goal of reaching the oldest ancestor of a given status, and stashing all statuses along the way. | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | func (d *deref) iterateAncestors(ctx context.Context, username string, statusIRI url.URL) error { | 
					
						
							| 
									
										
										
										
											2021-10-11 05:37:33 -07:00
										 |  |  | 	l := logrus.WithFields(logrus.Fields{ | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		"func":      "iterateAncestors", | 
					
						
							|  |  |  | 		"username":  username, | 
					
						
							|  |  |  | 		"statusIRI": statusIRI.String(), | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	l.Debug("entering iterateAncestors") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// if it's our status we don't need to dereference anything so we can immediately move up the chain | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 	host := viper.GetString(config.Keys.Host) | 
					
						
							|  |  |  | 	if statusIRI.Host == host { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		l.Debug("iri belongs to us, moving up to next ancestor") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// since this is our status, we know we can extract the id from the status path | 
					
						
							| 
									
										
										
										
											2021-12-20 15:19:53 +01:00
										 |  |  | 		_, id, err := uris.ParseStatusesPath(&statusIRI) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 		status, err := d.db.GetStatusByID(ctx, id) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if status.InReplyToURI == "" { | 
					
						
							|  |  |  | 			// status doesn't reply to anything | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		nextIRI, err := url.Parse(status.URI) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 		return d.iterateAncestors(ctx, username, *nextIRI) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-23 17:40:03 +02:00
										 |  |  | 	// If we reach here, we're looking at a remote status | 
					
						
							|  |  |  | 	_, statusable, err := d.GetRemoteStatus(ctx, username, &statusIRI, true, false) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		l.Debugf("error getting remote status: %s", err) | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	inReplyTo := ap.ExtractInReplyToURI(statusable) | 
					
						
							|  |  |  | 	if inReplyTo == nil || inReplyTo.String() == "" { | 
					
						
							|  |  |  | 		// status doesn't reply to anything | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// now move up to the next ancestor | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	return d.iterateAncestors(ctx, username, *inReplyTo) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | func (d *deref) iterateDescendants(ctx context.Context, username string, statusIRI url.URL, statusable ap.Statusable) error { | 
					
						
							| 
									
										
										
										
											2021-10-11 05:37:33 -07:00
										 |  |  | 	l := logrus.WithFields(logrus.Fields{ | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		"func":      "iterateDescendants", | 
					
						
							|  |  |  | 		"username":  username, | 
					
						
							|  |  |  | 		"statusIRI": statusIRI.String(), | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	l.Debug("entering iterateDescendants") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// if it's our status we already have descendants stashed so we can bail early | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 	host := viper.GetString(config.Keys.Host) | 
					
						
							|  |  |  | 	if statusIRI.Host == host { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		l.Debug("iri belongs to us, bailing") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	replies := statusable.GetActivityStreamsReplies() | 
					
						
							|  |  |  | 	if replies == nil || !replies.IsActivityStreamsCollection() { | 
					
						
							|  |  |  | 		l.Debug("no replies, bailing") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	repliesCollection := replies.GetActivityStreamsCollection() | 
					
						
							|  |  |  | 	if repliesCollection == nil { | 
					
						
							|  |  |  | 		l.Debug("replies collection is nil, bailing") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	first := repliesCollection.GetActivityStreamsFirst() | 
					
						
							|  |  |  | 	if first == nil { | 
					
						
							|  |  |  | 		l.Debug("replies collection has no first, bailing") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	firstPage := first.GetActivityStreamsCollectionPage() | 
					
						
							|  |  |  | 	if firstPage == nil { | 
					
						
							|  |  |  | 		l.Debug("first has no collection page, bailing") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	firstPageNext := firstPage.GetActivityStreamsNext() | 
					
						
							|  |  |  | 	if firstPageNext == nil || !firstPageNext.IsIRI() { | 
					
						
							|  |  |  | 		l.Debug("next is not an iri, bailing") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var foundReplies int | 
					
						
							|  |  |  | 	currentPageIRI := firstPageNext.GetIRI() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | pageLoop: | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		l.Debugf("dereferencing page %s", currentPageIRI) | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 		nextPage, err := d.DereferenceCollectionPage(ctx, username, currentPageIRI) | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2021-11-22 08:46:19 +01:00
										 |  |  | 			return err | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// next items could be either a list of URLs or a list of statuses | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		nextItems := nextPage.GetActivityStreamsItems() | 
					
						
							|  |  |  | 		if nextItems.Len() == 0 { | 
					
						
							|  |  |  | 			// no items on this page, which means we're done | 
					
						
							|  |  |  | 			break pageLoop | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// have a look through items and see what we can find | 
					
						
							|  |  |  | 		for iter := nextItems.Begin(); iter != nextItems.End(); iter = iter.Next() { | 
					
						
							|  |  |  | 			// We're looking for a url to feed to GetRemoteStatus. | 
					
						
							|  |  |  | 			// Items can be either an IRI, or a Note. | 
					
						
							|  |  |  | 			// If a note, we grab the ID from it and call it, rather than parsing the note. | 
					
						
							|  |  |  | 			var itemURI *url.URL | 
					
						
							| 
									
										
										
										
											2021-11-22 08:46:19 +01:00
										 |  |  | 			switch { | 
					
						
							|  |  |  | 			case iter.IsIRI(): | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 				// iri, easy | 
					
						
							|  |  |  | 				itemURI = iter.GetIRI() | 
					
						
							| 
									
										
										
										
											2021-11-22 08:46:19 +01:00
										 |  |  | 			case iter.IsActivityStreamsNote(): | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 				// note, get the id from it to use as iri | 
					
						
							|  |  |  | 				n := iter.GetActivityStreamsNote() | 
					
						
							|  |  |  | 				id := n.GetJSONLDId() | 
					
						
							|  |  |  | 				if id != nil && id.IsIRI() { | 
					
						
							|  |  |  | 					itemURI = id.GetIRI() | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2021-11-22 08:46:19 +01:00
										 |  |  | 			default: | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 				// if it's not an iri or a note, we don't know how to process it | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 			host := viper.GetString(config.Keys.Host) | 
					
						
							|  |  |  | 			if itemURI.Host == host { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 				// skip if the reply is from us -- we already have it then | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// we can confidently say now that we found something | 
					
						
							| 
									
										
										
										
											2021-11-22 08:46:19 +01:00
										 |  |  | 			foundReplies++ | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// get the remote statusable and put it in the db | 
					
						
							| 
									
										
										
										
											2022-05-24 11:00:37 +02:00
										 |  |  | 			_, statusable, err := d.GetRemoteStatus(ctx, username, itemURI, true, false) | 
					
						
							| 
									
										
										
										
											2022-05-23 17:40:03 +02:00
										 |  |  | 			if err == nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 				// now iterate descendants of *that* status | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 				if err := d.iterateDescendants(ctx, username, *itemURI, statusable); err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		next := nextPage.GetActivityStreamsNext() | 
					
						
							|  |  |  | 		if next != nil && next.IsIRI() { | 
					
						
							|  |  |  | 			l.Debug("setting next page") | 
					
						
							|  |  |  | 			currentPageIRI = next.GetIRI() | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			l.Debug("no next page, bailing") | 
					
						
							|  |  |  | 			break pageLoop | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	l.Debugf("foundReplies %d", foundReplies) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } |