mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 11:52:24 -05:00 
			
		
		
		
	[bugfix] support both CollectionPage AND OrderedCollectionPage in status replies (#2220)
This commit is contained in:
		
					parent
					
						
							
								8f67dd583d
							
						
					
				
			
			
				commit
				
					
						9f9fcf743d
					
				
			
		
					 5 changed files with 508 additions and 320 deletions
				
			
		|  | @ -17,15 +17,6 @@ | |||
| 
 | ||||
| package ap | ||||
| 
 | ||||
| import ( | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"github.com/superseriousbusiness/activity/streams" | ||||
| 	"github.com/superseriousbusiness/activity/streams/vocab" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/paging" | ||||
| ) | ||||
| 
 | ||||
| // https://www.w3.org/TR/activitystreams-vocabulary | ||||
| const ( | ||||
| 	ActivityAccept          = "Accept"          // ActivityStreamsAccept https://www.w3.org/TR/activitystreams-vocabulary/#dfn-accept | ||||
|  | @ -63,21 +54,22 @@ const ( | |||
| 	ActorPerson       = "Person"       // ActivityStreamsPerson https://www.w3.org/TR/activitystreams-vocabulary/#dfn-person | ||||
| 	ActorService      = "Service"      // ActivityStreamsService https://www.w3.org/TR/activitystreams-vocabulary/#dfn-service | ||||
| 
 | ||||
| 	ObjectArticle           = "Article"           // ActivityStreamsArticle https://www.w3.org/TR/activitystreams-vocabulary/#dfn-article | ||||
| 	ObjectAudio             = "Audio"             // ActivityStreamsAudio https://www.w3.org/TR/activitystreams-vocabulary/#dfn-audio | ||||
| 	ObjectDocument          = "Document"          // ActivityStreamsDocument https://www.w3.org/TR/activitystreams-vocabulary/#dfn-document | ||||
| 	ObjectEvent             = "Event"             // ActivityStreamsEvent https://www.w3.org/TR/activitystreams-vocabulary/#dfn-event | ||||
| 	ObjectImage             = "Image"             // ActivityStreamsImage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-image | ||||
| 	ObjectNote              = "Note"              // ActivityStreamsNote https://www.w3.org/TR/activitystreams-vocabulary/#dfn-note | ||||
| 	ObjectPage              = "Page"              // ActivityStreamsPage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-page | ||||
| 	ObjectPlace             = "Place"             // ActivityStreamsPlace https://www.w3.org/TR/activitystreams-vocabulary/#dfn-place | ||||
| 	ObjectProfile           = "Profile"           // ActivityStreamsProfile https://www.w3.org/TR/activitystreams-vocabulary/#dfn-profile | ||||
| 	ObjectRelationship      = "Relationship"      // ActivityStreamsRelationship https://www.w3.org/TR/activitystreams-vocabulary/#dfn-relationship | ||||
| 	ObjectTombstone         = "Tombstone"         // ActivityStreamsTombstone https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone | ||||
| 	ObjectVideo             = "Video"             // ActivityStreamsVideo https://www.w3.org/TR/activitystreams-vocabulary/#dfn-video | ||||
| 	ObjectCollection        = "Collection"        // ActivityStreamsCollection https://www.w3.org/TR/activitystreams-vocabulary/#dfn-collection | ||||
| 	ObjectCollectionPage    = "CollectionPage"    // ActivityStreamsCollectionPage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-collectionpage | ||||
| 	ObjectOrderedCollection = "OrderedCollection" // ActivityStreamsOrderedCollection https://www.w3.org/TR/activitystreams-vocabulary/#dfn-orderedcollection | ||||
| 	ObjectArticle               = "Article"               // ActivityStreamsArticle https://www.w3.org/TR/activitystreams-vocabulary/#dfn-article | ||||
| 	ObjectAudio                 = "Audio"                 // ActivityStreamsAudio https://www.w3.org/TR/activitystreams-vocabulary/#dfn-audio | ||||
| 	ObjectDocument              = "Document"              // ActivityStreamsDocument https://www.w3.org/TR/activitystreams-vocabulary/#dfn-document | ||||
| 	ObjectEvent                 = "Event"                 // ActivityStreamsEvent https://www.w3.org/TR/activitystreams-vocabulary/#dfn-event | ||||
| 	ObjectImage                 = "Image"                 // ActivityStreamsImage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-image | ||||
| 	ObjectNote                  = "Note"                  // ActivityStreamsNote https://www.w3.org/TR/activitystreams-vocabulary/#dfn-note | ||||
| 	ObjectPage                  = "Page"                  // ActivityStreamsPage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-page | ||||
| 	ObjectPlace                 = "Place"                 // ActivityStreamsPlace https://www.w3.org/TR/activitystreams-vocabulary/#dfn-place | ||||
| 	ObjectProfile               = "Profile"               // ActivityStreamsProfile https://www.w3.org/TR/activitystreams-vocabulary/#dfn-profile | ||||
| 	ObjectRelationship          = "Relationship"          // ActivityStreamsRelationship https://www.w3.org/TR/activitystreams-vocabulary/#dfn-relationship | ||||
| 	ObjectTombstone             = "Tombstone"             // ActivityStreamsTombstone https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone | ||||
| 	ObjectVideo                 = "Video"                 // ActivityStreamsVideo https://www.w3.org/TR/activitystreams-vocabulary/#dfn-video | ||||
| 	ObjectCollection            = "Collection"            // ActivityStreamsCollection https://www.w3.org/TR/activitystreams-vocabulary/#dfn-collection | ||||
| 	ObjectCollectionPage        = "CollectionPage"        // ActivityStreamsCollectionPage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-collectionpage | ||||
| 	ObjectOrderedCollection     = "OrderedCollection"     // ActivityStreamsOrderedCollection https://www.w3.org/TR/activitystreams-vocabulary/#dfn-orderedcollection | ||||
| 	ObjectOrderedCollectionPage = "OrderedCollectionPage" // ActivityStreamsOrderedCollectionPage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-orderedcollectionPage | ||||
| 
 | ||||
| 	// Hashtag is not in the AS spec per se, but it tends to get used | ||||
| 	// as though 'Hashtag' is a named type under the Tag property. | ||||
|  | @ -86,197 +78,3 @@ const ( | |||
| 	// and https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tag | ||||
| 	TagHashtag = "Hashtag" | ||||
| ) | ||||
| 
 | ||||
| type CollectionParams struct { | ||||
| 	// Containing collection | ||||
| 	// ID (i.e. NOT the page). | ||||
| 	ID *url.URL | ||||
| 
 | ||||
| 	// Total no. items. | ||||
| 	Total int | ||||
| } | ||||
| 
 | ||||
| type CollectionPageParams struct { | ||||
| 	// containing collection. | ||||
| 	CollectionParams | ||||
| 
 | ||||
| 	// Paging details. | ||||
| 	Current *paging.Page | ||||
| 	Next    *paging.Page | ||||
| 	Prev    *paging.Page | ||||
| 	Query   url.Values | ||||
| 
 | ||||
| 	// Item appender for each item at index. | ||||
| 	Append func(int, ItemsPropertyBuilder) | ||||
| 	Count  int | ||||
| } | ||||
| 
 | ||||
| // CollectionPage is a simplified interface type | ||||
| // that can be fulfilled by either of (where required): | ||||
| // vocab.ActivityStreamsCollection | ||||
| // vocab.ActivityStreamsOrderedCollection | ||||
| type CollectionBuilder interface { | ||||
| 	SetJSONLDId(vocab.JSONLDIdProperty) | ||||
| 	SetActivityStreamsFirst(vocab.ActivityStreamsFirstProperty) | ||||
| 	SetActivityStreamsTotalItems(i vocab.ActivityStreamsTotalItemsProperty) | ||||
| } | ||||
| 
 | ||||
| // CollectionPageBuilder is a simplified interface type | ||||
| // that can be fulfilled by either of (where required): | ||||
| // vocab.ActivityStreamsCollectionPage | ||||
| // vocab.ActivityStreamsOrderedCollectionPage | ||||
| type CollectionPageBuilder interface { | ||||
| 	SetJSONLDId(vocab.JSONLDIdProperty) | ||||
| 	SetActivityStreamsPartOf(vocab.ActivityStreamsPartOfProperty) | ||||
| 	SetActivityStreamsNext(vocab.ActivityStreamsNextProperty) | ||||
| 	SetActivityStreamsPrev(vocab.ActivityStreamsPrevProperty) | ||||
| 	SetActivityStreamsTotalItems(i vocab.ActivityStreamsTotalItemsProperty) | ||||
| } | ||||
| 
 | ||||
| // ItemsPropertyBuilder is a simplified interface type | ||||
| // that can be fulfilled by either of (where required): | ||||
| // vocab.ActivityStreamsItemsProperty | ||||
| // vocab.ActivityStreamsOrderedItemsProperty | ||||
| type ItemsPropertyBuilder interface { | ||||
| 	AppendIRI(*url.URL) | ||||
| 
 | ||||
| 	// NOTE: add more of the items-property-like interface | ||||
| 	// functions here as you require them for building pages. | ||||
| } | ||||
| 
 | ||||
| // NewASCollection builds and returns a new ActivityStreams Collection from given parameters. | ||||
| func NewASCollection(params CollectionParams) vocab.ActivityStreamsCollection { | ||||
| 	collection := streams.NewActivityStreamsCollection() | ||||
| 	buildCollection(collection, params, 40) | ||||
| 	return collection | ||||
| } | ||||
| 
 | ||||
| // NewASCollectionPage builds and returns a new ActivityStreams CollectionPage from given parameters (including item property appending function). | ||||
| func NewASCollectionPage(params CollectionPageParams) vocab.ActivityStreamsCollectionPage { | ||||
| 	collectionPage := streams.NewActivityStreamsCollectionPage() | ||||
| 	itemsProp := streams.NewActivityStreamsItemsProperty() | ||||
| 	buildCollectionPage(collectionPage, itemsProp, collectionPage.SetActivityStreamsItems, params) | ||||
| 	return collectionPage | ||||
| } | ||||
| 
 | ||||
| // NewASOrderedCollection builds and returns a new ActivityStreams OrderedCollection from given parameters. | ||||
| func NewASOrderedCollection(params CollectionParams) vocab.ActivityStreamsOrderedCollection { | ||||
| 	collection := streams.NewActivityStreamsOrderedCollection() | ||||
| 	buildCollection(collection, params, 40) | ||||
| 	return collection | ||||
| } | ||||
| 
 | ||||
| // NewASOrderedCollectionPage builds and returns a new ActivityStreams OrderedCollectionPage from given parameters (including item property appending function). | ||||
| func NewASOrderedCollectionPage(params CollectionPageParams) vocab.ActivityStreamsOrderedCollectionPage { | ||||
| 	collectionPage := streams.NewActivityStreamsOrderedCollectionPage() | ||||
| 	itemsProp := streams.NewActivityStreamsOrderedItemsProperty() | ||||
| 	buildCollectionPage(collectionPage, itemsProp, collectionPage.SetActivityStreamsOrderedItems, params) | ||||
| 	return collectionPage | ||||
| } | ||||
| 
 | ||||
| func buildCollection[C CollectionBuilder](collection C, params CollectionParams, pageLimit int) { | ||||
| 	// Add the collection ID property. | ||||
| 	idProp := streams.NewJSONLDIdProperty() | ||||
| 	idProp.SetIRI(params.ID) | ||||
| 	collection.SetJSONLDId(idProp) | ||||
| 
 | ||||
| 	// Add the collection totalItems count property. | ||||
| 	totalItems := streams.NewActivityStreamsTotalItemsProperty() | ||||
| 	totalItems.Set(params.Total) | ||||
| 	collection.SetActivityStreamsTotalItems(totalItems) | ||||
| 
 | ||||
| 	// Clone the collection ID page | ||||
| 	// to add first page query data. | ||||
| 	firstIRI := new(url.URL) | ||||
| 	*firstIRI = *params.ID | ||||
| 
 | ||||
| 	// Note that simply adding a limit signals to our | ||||
| 	// endpoint to use paging (which will start at beginning). | ||||
| 	limit := "limit=" + strconv.Itoa(pageLimit) | ||||
| 	firstIRI.RawQuery = appendQuery(firstIRI.RawQuery, limit) | ||||
| 
 | ||||
| 	// Add the collection first IRI property. | ||||
| 	first := streams.NewActivityStreamsFirstProperty() | ||||
| 	first.SetIRI(firstIRI) | ||||
| 	collection.SetActivityStreamsFirst(first) | ||||
| } | ||||
| 
 | ||||
| func buildCollectionPage[C CollectionPageBuilder, I ItemsPropertyBuilder](collectionPage C, itemsProp I, setItems func(I), params CollectionPageParams) { | ||||
| 	// Add the partOf property for its containing collection ID. | ||||
| 	partOfProp := streams.NewActivityStreamsPartOfProperty() | ||||
| 	partOfProp.SetIRI(params.ID) | ||||
| 	collectionPage.SetActivityStreamsPartOf(partOfProp) | ||||
| 
 | ||||
| 	// Build the current page link IRI. | ||||
| 	currentIRI := params.Current.ToLinkURL( | ||||
| 		params.ID.Scheme, | ||||
| 		params.ID.Host, | ||||
| 		params.ID.Path, | ||||
| 		params.Query, | ||||
| 	) | ||||
| 
 | ||||
| 	// Add the collection ID property for | ||||
| 	// the *current* collection page params. | ||||
| 	idProp := streams.NewJSONLDIdProperty() | ||||
| 	idProp.SetIRI(currentIRI) | ||||
| 	collectionPage.SetJSONLDId(idProp) | ||||
| 
 | ||||
| 	// Build the next page link IRI. | ||||
| 	nextIRI := params.Next.ToLinkURL( | ||||
| 		params.ID.Scheme, | ||||
| 		params.ID.Host, | ||||
| 		params.ID.Path, | ||||
| 		params.Query, | ||||
| 	) | ||||
| 
 | ||||
| 	if nextIRI != nil { | ||||
| 		// Add the collection next property for the next page. | ||||
| 		nextProp := streams.NewActivityStreamsNextProperty() | ||||
| 		nextProp.SetIRI(nextIRI) | ||||
| 		collectionPage.SetActivityStreamsNext(nextProp) | ||||
| 	} | ||||
| 
 | ||||
| 	// Build the prev page link IRI. | ||||
| 	prevIRI := params.Prev.ToLinkURL( | ||||
| 		params.ID.Scheme, | ||||
| 		params.ID.Host, | ||||
| 		params.ID.Path, | ||||
| 		params.Query, | ||||
| 	) | ||||
| 
 | ||||
| 	if prevIRI != nil { | ||||
| 		// Add the collection prev property for the prev page. | ||||
| 		prevProp := streams.NewActivityStreamsPrevProperty() | ||||
| 		prevProp.SetIRI(prevIRI) | ||||
| 		collectionPage.SetActivityStreamsPrev(prevProp) | ||||
| 	} | ||||
| 
 | ||||
| 	// Add the collection totalItems count property. | ||||
| 	totalItems := streams.NewActivityStreamsTotalItemsProperty() | ||||
| 	totalItems.Set(params.Total) | ||||
| 	collectionPage.SetActivityStreamsTotalItems(totalItems) | ||||
| 
 | ||||
| 	if params.Append == nil { | ||||
| 		// nil check outside the for loop. | ||||
| 		panic("nil params.Append function") | ||||
| 	} | ||||
| 
 | ||||
| 	// Append each of the items to the provided | ||||
| 	// pre-allocated items property builder type. | ||||
| 	for i := 0; i < params.Count; i++ { | ||||
| 		params.Append(i, itemsProp) | ||||
| 	} | ||||
| 
 | ||||
| 	// Set the collection | ||||
| 	// page items property. | ||||
| 	setItems(itemsProp) | ||||
| } | ||||
| 
 | ||||
| // appendQuery appends part to an existing raw | ||||
| // query with ampersand, else just returning part. | ||||
| func appendQuery(raw, part string) string { | ||||
| 	if raw != "" { | ||||
| 		return raw + "&" + part | ||||
| 	} | ||||
| 	return part | ||||
| } | ||||
|  |  | |||
							
								
								
									
										359
									
								
								internal/ap/collections.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										359
									
								
								internal/ap/collections.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,359 @@ | |||
| // 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/>. | ||||
| 
 | ||||
| package ap | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"github.com/superseriousbusiness/activity/streams" | ||||
| 	"github.com/superseriousbusiness/activity/streams/vocab" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/paging" | ||||
| ) | ||||
| 
 | ||||
| // ToCollectionPageIterator attempts to resolve the given vocab type as a CollectionPage | ||||
| // like object and wrap in a standardised interface in order to iterate its contents. | ||||
| func ToCollectionPageIterator(t vocab.Type) (CollectionPageIterator, error) { | ||||
| 	switch name := t.GetTypeName(); name { | ||||
| 	case ObjectCollectionPage: | ||||
| 		t := t.(vocab.ActivityStreamsCollectionPage) //nolint:forcetypeassert | ||||
| 		return WrapCollectionPage(t), nil | ||||
| 	case ObjectOrderedCollectionPage: | ||||
| 		t := t.(vocab.ActivityStreamsOrderedCollectionPage) //nolint:forcetypeassert | ||||
| 		return WrapOrderedCollectionPage(t), nil | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("%T(%s) was not CollectionPage-like", t, name) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // WrapCollectionPage wraps an ActivityStreamsCollectionPage in a standardised collection page interface. | ||||
| func WrapCollectionPage(page vocab.ActivityStreamsCollectionPage) CollectionPageIterator { | ||||
| 	return ®ularCollectionPageIterator{ActivityStreamsCollectionPage: page} | ||||
| } | ||||
| 
 | ||||
| // WrapOrderedCollectionPage wraps an ActivityStreamsOrderedCollectionPage in a standardised collection page interface. | ||||
| func WrapOrderedCollectionPage(page vocab.ActivityStreamsOrderedCollectionPage) CollectionPageIterator { | ||||
| 	return &orderedCollectionPageIterator{ActivityStreamsOrderedCollectionPage: page} | ||||
| } | ||||
| 
 | ||||
| // regularCollectionPageIterator implements CollectionPageIterator | ||||
| // for the vocab.ActivitiyStreamsCollectionPage type. | ||||
| type regularCollectionPageIterator struct { | ||||
| 	vocab.ActivityStreamsCollectionPage | ||||
| 	items vocab.ActivityStreamsItemsPropertyIterator | ||||
| 	once  bool // only init items once | ||||
| } | ||||
| 
 | ||||
| func (iter *regularCollectionPageIterator) NextPage() WithIRI { | ||||
| 	if iter.ActivityStreamsCollectionPage == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return iter.GetActivityStreamsNext() | ||||
| } | ||||
| 
 | ||||
| func (iter *regularCollectionPageIterator) PrevPage() WithIRI { | ||||
| 	if iter.ActivityStreamsCollectionPage == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return iter.GetActivityStreamsPrev() | ||||
| } | ||||
| 
 | ||||
| func (iter *regularCollectionPageIterator) NextItem() IteratorItemable { | ||||
| 	if !iter.initItems() { | ||||
| 		return nil | ||||
| 	} | ||||
| 	cur := iter.items | ||||
| 	iter.items = iter.items.Next() | ||||
| 	return cur | ||||
| } | ||||
| 
 | ||||
| func (iter *regularCollectionPageIterator) PrevItem() IteratorItemable { | ||||
| 	if !iter.initItems() { | ||||
| 		return nil | ||||
| 	} | ||||
| 	cur := iter.items | ||||
| 	iter.items = iter.items.Prev() | ||||
| 	return cur | ||||
| } | ||||
| 
 | ||||
| func (iter *regularCollectionPageIterator) initItems() bool { | ||||
| 	if iter.once { | ||||
| 		return (iter.items != nil) | ||||
| 	} | ||||
| 	iter.once = true | ||||
| 	if iter.ActivityStreamsCollectionPage == nil { | ||||
| 		return false // no page set | ||||
| 	} | ||||
| 	items := iter.GetActivityStreamsItems() | ||||
| 	if items == nil { | ||||
| 		return false // no items found | ||||
| 	} | ||||
| 	iter.items = items.Begin() | ||||
| 	return (iter.items != nil) | ||||
| } | ||||
| 
 | ||||
| // orderedCollectionPageIterator implements CollectionPageIterator | ||||
| // for the vocab.ActivitiyStreamsOrderedCollectionPage type. | ||||
| type orderedCollectionPageIterator struct { | ||||
| 	vocab.ActivityStreamsOrderedCollectionPage | ||||
| 	items vocab.ActivityStreamsOrderedItemsPropertyIterator | ||||
| 	once  bool // only init items once | ||||
| } | ||||
| 
 | ||||
| func (iter *orderedCollectionPageIterator) NextPage() WithIRI { | ||||
| 	if iter.ActivityStreamsOrderedCollectionPage == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return iter.GetActivityStreamsNext() | ||||
| } | ||||
| 
 | ||||
| func (iter *orderedCollectionPageIterator) PrevPage() WithIRI { | ||||
| 	if iter.ActivityStreamsOrderedCollectionPage == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return iter.GetActivityStreamsPrev() | ||||
| } | ||||
| 
 | ||||
| func (iter *orderedCollectionPageIterator) NextItem() IteratorItemable { | ||||
| 	if !iter.initItems() { | ||||
| 		return nil | ||||
| 	} | ||||
| 	cur := iter.items | ||||
| 	iter.items = iter.items.Next() | ||||
| 	return cur | ||||
| } | ||||
| 
 | ||||
| func (iter *orderedCollectionPageIterator) PrevItem() IteratorItemable { | ||||
| 	if !iter.initItems() { | ||||
| 		return nil | ||||
| 	} | ||||
| 	cur := iter.items | ||||
| 	iter.items = iter.items.Prev() | ||||
| 	return cur | ||||
| } | ||||
| 
 | ||||
| func (iter *orderedCollectionPageIterator) initItems() bool { | ||||
| 	if iter.once { | ||||
| 		return (iter.items != nil) | ||||
| 	} | ||||
| 	iter.once = true | ||||
| 	if iter.ActivityStreamsOrderedCollectionPage == nil { | ||||
| 		return false // no page set | ||||
| 	} | ||||
| 	items := iter.GetActivityStreamsOrderedItems() | ||||
| 	if items == nil { | ||||
| 		return false // no items found | ||||
| 	} | ||||
| 	iter.items = items.Begin() | ||||
| 	return (iter.items != nil) | ||||
| } | ||||
| 
 | ||||
| type CollectionParams struct { | ||||
| 	// Containing collection | ||||
| 	// ID (i.e. NOT the page). | ||||
| 	ID *url.URL | ||||
| 
 | ||||
| 	// Total no. items. | ||||
| 	Total int | ||||
| } | ||||
| 
 | ||||
| type CollectionPageParams struct { | ||||
| 	// containing collection. | ||||
| 	CollectionParams | ||||
| 
 | ||||
| 	// Paging details. | ||||
| 	Current *paging.Page | ||||
| 	Next    *paging.Page | ||||
| 	Prev    *paging.Page | ||||
| 	Query   url.Values | ||||
| 
 | ||||
| 	// Item appender for each item at index. | ||||
| 	Append func(int, ItemsPropertyBuilder) | ||||
| 	Count  int | ||||
| } | ||||
| 
 | ||||
| // CollectionPage is a simplified interface type | ||||
| // that can be fulfilled by either of (where required): | ||||
| // vocab.ActivityStreamsCollection | ||||
| // vocab.ActivityStreamsOrderedCollection | ||||
| type CollectionBuilder interface { | ||||
| 	SetJSONLDId(vocab.JSONLDIdProperty) | ||||
| 	SetActivityStreamsFirst(vocab.ActivityStreamsFirstProperty) | ||||
| 	SetActivityStreamsTotalItems(i vocab.ActivityStreamsTotalItemsProperty) | ||||
| } | ||||
| 
 | ||||
| // CollectionPageBuilder is a simplified interface type | ||||
| // that can be fulfilled by either of (where required): | ||||
| // vocab.ActivityStreamsCollectionPage | ||||
| // vocab.ActivityStreamsOrderedCollectionPage | ||||
| type CollectionPageBuilder interface { | ||||
| 	SetJSONLDId(vocab.JSONLDIdProperty) | ||||
| 	SetActivityStreamsPartOf(vocab.ActivityStreamsPartOfProperty) | ||||
| 	SetActivityStreamsNext(vocab.ActivityStreamsNextProperty) | ||||
| 	SetActivityStreamsPrev(vocab.ActivityStreamsPrevProperty) | ||||
| 	SetActivityStreamsTotalItems(i vocab.ActivityStreamsTotalItemsProperty) | ||||
| } | ||||
| 
 | ||||
| // ItemsPropertyBuilder is a simplified interface type | ||||
| // that can be fulfilled by either of (where required): | ||||
| // vocab.ActivityStreamsItemsProperty | ||||
| // vocab.ActivityStreamsOrderedItemsProperty | ||||
| type ItemsPropertyBuilder interface { | ||||
| 	AppendIRI(*url.URL) | ||||
| 
 | ||||
| 	// NOTE: add more of the items-property-like interface | ||||
| 	// functions here as you require them for building pages. | ||||
| } | ||||
| 
 | ||||
| // NewASCollection builds and returns a new ActivityStreams Collection from given parameters. | ||||
| func NewASCollection(params CollectionParams) vocab.ActivityStreamsCollection { | ||||
| 	collection := streams.NewActivityStreamsCollection() | ||||
| 	buildCollection(collection, params, 40) | ||||
| 	return collection | ||||
| } | ||||
| 
 | ||||
| // NewASCollectionPage builds and returns a new ActivityStreams CollectionPage from given parameters (including item property appending function). | ||||
| func NewASCollectionPage(params CollectionPageParams) vocab.ActivityStreamsCollectionPage { | ||||
| 	collectionPage := streams.NewActivityStreamsCollectionPage() | ||||
| 	itemsProp := streams.NewActivityStreamsItemsProperty() | ||||
| 	buildCollectionPage(collectionPage, itemsProp, collectionPage.SetActivityStreamsItems, params) | ||||
| 	return collectionPage | ||||
| } | ||||
| 
 | ||||
| // NewASOrderedCollection builds and returns a new ActivityStreams OrderedCollection from given parameters. | ||||
| func NewASOrderedCollection(params CollectionParams) vocab.ActivityStreamsOrderedCollection { | ||||
| 	collection := streams.NewActivityStreamsOrderedCollection() | ||||
| 	buildCollection(collection, params, 40) | ||||
| 	return collection | ||||
| } | ||||
| 
 | ||||
| // NewASOrderedCollectionPage builds and returns a new ActivityStreams OrderedCollectionPage from given parameters (including item property appending function). | ||||
| func NewASOrderedCollectionPage(params CollectionPageParams) vocab.ActivityStreamsOrderedCollectionPage { | ||||
| 	collectionPage := streams.NewActivityStreamsOrderedCollectionPage() | ||||
| 	itemsProp := streams.NewActivityStreamsOrderedItemsProperty() | ||||
| 	buildCollectionPage(collectionPage, itemsProp, collectionPage.SetActivityStreamsOrderedItems, params) | ||||
| 	return collectionPage | ||||
| } | ||||
| 
 | ||||
| func buildCollection[C CollectionBuilder](collection C, params CollectionParams, pageLimit int) { | ||||
| 	// Add the collection ID property. | ||||
| 	idProp := streams.NewJSONLDIdProperty() | ||||
| 	idProp.SetIRI(params.ID) | ||||
| 	collection.SetJSONLDId(idProp) | ||||
| 
 | ||||
| 	// Add the collection totalItems count property. | ||||
| 	totalItems := streams.NewActivityStreamsTotalItemsProperty() | ||||
| 	totalItems.Set(params.Total) | ||||
| 	collection.SetActivityStreamsTotalItems(totalItems) | ||||
| 
 | ||||
| 	// Clone the collection ID page | ||||
| 	// to add first page query data. | ||||
| 	firstIRI := new(url.URL) | ||||
| 	*firstIRI = *params.ID | ||||
| 
 | ||||
| 	// Note that simply adding a limit signals to our | ||||
| 	// endpoint to use paging (which will start at beginning). | ||||
| 	limit := "limit=" + strconv.Itoa(pageLimit) | ||||
| 	firstIRI.RawQuery = appendQuery(firstIRI.RawQuery, limit) | ||||
| 
 | ||||
| 	// Add the collection first IRI property. | ||||
| 	first := streams.NewActivityStreamsFirstProperty() | ||||
| 	first.SetIRI(firstIRI) | ||||
| 	collection.SetActivityStreamsFirst(first) | ||||
| } | ||||
| 
 | ||||
| func buildCollectionPage[C CollectionPageBuilder, I ItemsPropertyBuilder](collectionPage C, itemsProp I, setItems func(I), params CollectionPageParams) { | ||||
| 	// Add the partOf property for its containing collection ID. | ||||
| 	partOfProp := streams.NewActivityStreamsPartOfProperty() | ||||
| 	partOfProp.SetIRI(params.ID) | ||||
| 	collectionPage.SetActivityStreamsPartOf(partOfProp) | ||||
| 
 | ||||
| 	// Build the current page link IRI. | ||||
| 	currentIRI := params.Current.ToLinkURL( | ||||
| 		params.ID.Scheme, | ||||
| 		params.ID.Host, | ||||
| 		params.ID.Path, | ||||
| 		params.Query, | ||||
| 	) | ||||
| 
 | ||||
| 	// Add the collection ID property for | ||||
| 	// the *current* collection page params. | ||||
| 	idProp := streams.NewJSONLDIdProperty() | ||||
| 	idProp.SetIRI(currentIRI) | ||||
| 	collectionPage.SetJSONLDId(idProp) | ||||
| 
 | ||||
| 	// Build the next page link IRI. | ||||
| 	nextIRI := params.Next.ToLinkURL( | ||||
| 		params.ID.Scheme, | ||||
| 		params.ID.Host, | ||||
| 		params.ID.Path, | ||||
| 		params.Query, | ||||
| 	) | ||||
| 
 | ||||
| 	if nextIRI != nil { | ||||
| 		// Add the collection next property for the next page. | ||||
| 		nextProp := streams.NewActivityStreamsNextProperty() | ||||
| 		nextProp.SetIRI(nextIRI) | ||||
| 		collectionPage.SetActivityStreamsNext(nextProp) | ||||
| 	} | ||||
| 
 | ||||
| 	// Build the prev page link IRI. | ||||
| 	prevIRI := params.Prev.ToLinkURL( | ||||
| 		params.ID.Scheme, | ||||
| 		params.ID.Host, | ||||
| 		params.ID.Path, | ||||
| 		params.Query, | ||||
| 	) | ||||
| 
 | ||||
| 	if prevIRI != nil { | ||||
| 		// Add the collection prev property for the prev page. | ||||
| 		prevProp := streams.NewActivityStreamsPrevProperty() | ||||
| 		prevProp.SetIRI(prevIRI) | ||||
| 		collectionPage.SetActivityStreamsPrev(prevProp) | ||||
| 	} | ||||
| 
 | ||||
| 	// Add the collection totalItems count property. | ||||
| 	totalItems := streams.NewActivityStreamsTotalItemsProperty() | ||||
| 	totalItems.Set(params.Total) | ||||
| 	collectionPage.SetActivityStreamsTotalItems(totalItems) | ||||
| 
 | ||||
| 	if params.Append == nil { | ||||
| 		// nil check outside the for loop. | ||||
| 		panic("nil params.Append function") | ||||
| 	} | ||||
| 
 | ||||
| 	// Append each of the items to the provided | ||||
| 	// pre-allocated items property builder type. | ||||
| 	for i := 0; i < params.Count; i++ { | ||||
| 		params.Append(i, itemsProp) | ||||
| 	} | ||||
| 
 | ||||
| 	// Set the collection | ||||
| 	// page items property. | ||||
| 	setItems(itemsProp) | ||||
| } | ||||
| 
 | ||||
| // appendQuery appends part to an existing raw | ||||
| // query with ampersand, else just returning part. | ||||
| func appendQuery(raw, part string) string { | ||||
| 	if raw != "" { | ||||
| 		return raw + "&" + part | ||||
| 	} | ||||
| 	return part | ||||
| } | ||||
|  | @ -17,7 +17,11 @@ | |||
| 
 | ||||
| package ap | ||||
| 
 | ||||
| import "github.com/superseriousbusiness/activity/streams/vocab" | ||||
| import ( | ||||
| 	"net/url" | ||||
| 
 | ||||
| 	"github.com/superseriousbusiness/activity/streams/vocab" | ||||
| ) | ||||
| 
 | ||||
| // Accountable represents the minimum activitypub interface for representing an 'account'. | ||||
| // This interface is fulfilled by: Person, Application, Organization, Service, and Group | ||||
|  | @ -153,14 +157,23 @@ type ReplyToable interface { | |||
| 	WithInReplyTo | ||||
| } | ||||
| 
 | ||||
| // CollectionPageable represents the minimum interface for an activitystreams 'CollectionPage' object. | ||||
| type CollectionPageable interface { | ||||
| // CollectionPageIterator represents the minimum interface for interacting with a wrapped | ||||
| // CollectionPage or OrderedCollectionPage in order to access both next / prev pages and items. | ||||
| type CollectionPageIterator interface { | ||||
| 	WithJSONLDId | ||||
| 	WithTypeName | ||||
| 
 | ||||
| 	WithNext | ||||
| 	WithPartOf | ||||
| 	WithItems | ||||
| 	NextPage() WithIRI | ||||
| 	PrevPage() WithIRI | ||||
| 
 | ||||
| 	NextItem() IteratorItemable | ||||
| 	PrevItem() IteratorItemable | ||||
| } | ||||
| 
 | ||||
| // IteratorItemable represents the minimum interface for an item in an iterator. | ||||
| type IteratorItemable interface { | ||||
| 	WithIRI | ||||
| 	WithType | ||||
| } | ||||
| 
 | ||||
| // Flaggable represents the minimum interface for an activitystreams 'Flag' activity. | ||||
|  | @ -173,11 +186,22 @@ type Flaggable interface { | |||
| 	WithObject | ||||
| } | ||||
| 
 | ||||
| // WithJSONLDId represents an activity with JSONLDIdProperty | ||||
| // WithJSONLDId represents an activity with JSONLDIdProperty. | ||||
| type WithJSONLDId interface { | ||||
| 	GetJSONLDId() vocab.JSONLDIdProperty | ||||
| } | ||||
| 
 | ||||
| // WithIRI represents an object (possibly) representable as an IRI. | ||||
| type WithIRI interface { | ||||
| 	GetIRI() *url.URL | ||||
| 	IsIRI() bool | ||||
| } | ||||
| 
 | ||||
| // WithType ... | ||||
| type WithType interface { | ||||
| 	GetType() vocab.Type | ||||
| } | ||||
| 
 | ||||
| // WithTypeName represents an activity with a type name | ||||
| type WithTypeName interface { | ||||
| 	GetTypeName() string | ||||
|  |  | |||
|  | @ -20,49 +20,115 @@ package dereferencing | |||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 
 | ||||
| 	"github.com/superseriousbusiness/activity/streams" | ||||
| 	"github.com/superseriousbusiness/activity/streams/vocab" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/log" | ||||
| ) | ||||
| 
 | ||||
| // dereferenceCollectionPage returns the activitystreams CollectionPage at the specified IRI, or an error if something goes wrong. | ||||
| func (d *deref) dereferenceCollectionPage(ctx context.Context, username string, pageIRI *url.URL) (ap.CollectionPageable, error) { | ||||
| func (d *deref) dereferenceCollectionPage(ctx context.Context, username string, pageIRI *url.URL) (ap.CollectionPageIterator, error) { | ||||
| 	if blocked, err := d.state.DB.IsDomainBlocked(ctx, pageIRI.Host); blocked || err != nil { | ||||
| 		return nil, fmt.Errorf("DereferenceCollectionPage: domain %s is blocked", pageIRI.Host) | ||||
| 		return nil, gtserror.Newf("domain %s is blocked", pageIRI.Host) | ||||
| 	} | ||||
| 
 | ||||
| 	transport, err := d.transportController.NewTransportForUsername(ctx, username) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("DereferenceCollectionPage: error creating transport: %s", err) | ||||
| 		return nil, gtserror.Newf("error creating transport: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	b, err := transport.Dereference(ctx, pageIRI) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("DereferenceCollectionPage: error deferencing %s: %s", pageIRI.String(), err) | ||||
| 		return nil, gtserror.Newf("error deferencing %s: %w", pageIRI.String(), err) | ||||
| 	} | ||||
| 
 | ||||
| 	m := make(map[string]interface{}) | ||||
| 	if err := json.Unmarshal(b, &m); err != nil { | ||||
| 		return nil, fmt.Errorf("DereferenceCollectionPage: error unmarshalling bytes into json: %s", err) | ||||
| 		return nil, gtserror.Newf("error unmarshalling bytes into json: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	t, err := streams.ToType(ctx, m) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("DereferenceCollectionPage: error resolving json into ap vocab type: %s", err) | ||||
| 		return nil, gtserror.Newf("error resolving json into ap vocab type: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if t.GetTypeName() != ap.ObjectCollectionPage { | ||||
| 		return nil, fmt.Errorf("DereferenceCollectionPage: type name %s not supported", t.GetTypeName()) | ||||
| 	page, err := ap.ToCollectionPageIterator(t) | ||||
| 	if err != nil { | ||||
| 		return nil, gtserror.Newf("error resolving vocab type as page: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	p, ok := t.(vocab.ActivityStreamsCollectionPage) | ||||
| 	if !ok { | ||||
| 		return nil, errors.New("DereferenceCollectionPage: error resolving type as activitystreams collection page") | ||||
| 	} | ||||
| 
 | ||||
| 	return p, nil | ||||
| 	return page, nil | ||||
| } | ||||
| 
 | ||||
| // getAttachedStatusCollection is a small utility function to fetch the first page of an | ||||
| // attached activity streams collection from a provided statusable object, along with a URI. | ||||
| func getAttachedStatusCollectionPage(status ap.Statusable) (ap.CollectionPageIterator, string) { //nolint:gocritic | ||||
| 	// Look for an attached status replies (as collection) | ||||
| 	replies := status.GetActivityStreamsReplies() | ||||
| 	if replies == nil { | ||||
| 		return nil, "" | ||||
| 	} | ||||
| 
 | ||||
| 	// Look for an attached collection page, wrap and return. | ||||
| 	if page := getRepliesCollectionPage(replies); page != nil { | ||||
| 		return ap.WrapCollectionPage(page), getIDString(page) | ||||
| 	} | ||||
| 
 | ||||
| 	// Look for an attached ordered collection page, wrap and return. | ||||
| 	if page := getRepliesOrderedCollectionPage(replies); page != nil { | ||||
| 		return ap.WrapOrderedCollectionPage(page), getIDString(page) | ||||
| 	} | ||||
| 
 | ||||
| 	log.Warnf(nil, "replies without collection page: %s", getIDString(status)) | ||||
| 	return nil, "" | ||||
| } | ||||
| 
 | ||||
| func getRepliesCollectionPage(replies vocab.ActivityStreamsRepliesProperty) vocab.ActivityStreamsCollectionPage { | ||||
| 	// Get the status replies collection | ||||
| 	collection := replies.GetActivityStreamsCollection() | ||||
| 	if collection == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Get the "first" property of the replies collection | ||||
| 	first := collection.GetActivityStreamsFirst() | ||||
| 	if first == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Return the first activity stream collection page | ||||
| 	return first.GetActivityStreamsCollectionPage() | ||||
| } | ||||
| 
 | ||||
| func getRepliesOrderedCollectionPage(replies vocab.ActivityStreamsRepliesProperty) vocab.ActivityStreamsOrderedCollectionPage { | ||||
| 	// Get the status replies collection | ||||
| 	collection := replies.GetActivityStreamsOrderedCollection() | ||||
| 	if collection == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Get the "first" property of the replies collection | ||||
| 	first := collection.GetActivityStreamsFirst() | ||||
| 	if first == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Return the first activity stream collection page | ||||
| 	return first.GetActivityStreamsOrderedCollectionPage() | ||||
| } | ||||
| 
 | ||||
| // getIDString is shorthand to fetch an ID URI string from AP type with attached JSONLDId. | ||||
| func getIDString(a ap.WithJSONLDId) string { | ||||
| 	id := a.GetJSONLDId() | ||||
| 	if id == nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	uri := id.Get() | ||||
| 	if uri == nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return uri.String() | ||||
| } | ||||
|  |  | |||
|  | @ -25,7 +25,6 @@ import ( | |||
| 
 | ||||
| 	"codeberg.org/gruf/go-kv" | ||||
| 	"github.com/superseriousbusiness/activity/pub" | ||||
| 	"github.com/superseriousbusiness/activity/streams/vocab" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
|  | @ -247,15 +246,12 @@ func (d *deref) DereferenceStatusDescendants(ctx context.Context, username strin | |||
| 		// page is the current activity streams | ||||
| 		// collection page we are on (as we often | ||||
| 		// push a frame to stack mid-paging). | ||||
| 		page ap.CollectionPageable | ||||
| 		page ap.CollectionPageIterator | ||||
| 
 | ||||
| 		// pageURI is the URI string of | ||||
| 		// the frame's collection page | ||||
| 		// (is useful for logging). | ||||
| 		pageURI string | ||||
| 
 | ||||
| 		// items is the entity iterator for frame's page. | ||||
| 		items vocab.ActivityStreamsItemsPropertyIterator | ||||
| 	} | ||||
| 
 | ||||
| 	var ( | ||||
|  | @ -270,7 +266,7 @@ func (d *deref) DereferenceStatusDescendants(ctx context.Context, username strin | |||
| 		stack = []*frame{ | ||||
| 			func() *frame { | ||||
| 				// Start input frame is built from the first input. | ||||
| 				page, pageURI := getAttachedStatusCollection(parent) | ||||
| 				page, pageURI := getAttachedStatusCollectionPage(parent) | ||||
| 				if page == nil { | ||||
| 					return nil | ||||
| 				} | ||||
|  | @ -305,34 +301,18 @@ stackLoop: | |||
| 
 | ||||
| 	pageLoop: | ||||
| 		for { | ||||
| 			if current.items == nil { | ||||
| 				// Get the items associated with this page | ||||
| 				items := current.page.GetActivityStreamsItems() | ||||
| 				if items == nil { | ||||
| 					continue stackLoop | ||||
| 				} | ||||
| 
 | ||||
| 				// Start off the item iterator | ||||
| 				current.items = items.Begin() | ||||
| 			} | ||||
| 
 | ||||
| 			l.Tracef("following collection page: %s", current.pageURI) | ||||
| 
 | ||||
| 		itemLoop: | ||||
| 			for { | ||||
| 				// Check for remaining iter | ||||
| 				if current.items == nil { | ||||
| 				// Get next item from page iter. | ||||
| 				next := current.page.NextItem() | ||||
| 				if next == nil { | ||||
| 					break itemLoop | ||||
| 				} | ||||
| 
 | ||||
| 				// Get current item iterator | ||||
| 				itemIter := current.items | ||||
| 
 | ||||
| 				// Set the next available iterator | ||||
| 				current.items = itemIter.Next() | ||||
| 
 | ||||
| 				// Check for available IRI on item | ||||
| 				itemIRI, _ := pub.ToId(itemIter) | ||||
| 				itemIRI, _ := pub.ToId(next) | ||||
| 				if itemIRI == nil { | ||||
| 					continue itemLoop | ||||
| 				} | ||||
|  | @ -364,8 +344,8 @@ stackLoop: | |||
| 					continue itemLoop | ||||
| 				} | ||||
| 
 | ||||
| 				// Extract any attached collection + URI from status. | ||||
| 				page, pageURI := getAttachedStatusCollection(statusable) | ||||
| 				// Extract any attached collection + ID URI from status. | ||||
| 				page, pageURI := getAttachedStatusCollectionPage(statusable) | ||||
| 				if page == nil { | ||||
| 					continue itemLoop | ||||
| 				} | ||||
|  | @ -380,80 +360,41 @@ stackLoop: | |||
| 				continue stackLoop | ||||
| 			} | ||||
| 
 | ||||
| 			// Get the current page's "next" property. | ||||
| 			pageNext := current.page.GetActivityStreamsNext() | ||||
| 			if pageNext == nil || !pageNext.IsIRI() { | ||||
| 			// Get the next page from iterator. | ||||
| 			next := current.page.NextPage() | ||||
| 			if next == nil || !next.IsIRI() { | ||||
| 				continue stackLoop | ||||
| 			} | ||||
| 
 | ||||
| 			// Get the IRI of the "next" property. | ||||
| 			pageNextURI := pageNext.GetIRI() | ||||
| 			pageNextURIStr := pageNextURI.String() | ||||
| 			// Get the next page IRI. | ||||
| 			nextURI := next.GetIRI() | ||||
| 			nextURIStr := nextURI.String() | ||||
| 
 | ||||
| 			// Check whether this page has already been deref'd. | ||||
| 			if _, ok := derefdPages[pageNextURIStr]; ok { | ||||
| 				l.Warnf("self referencing collection page(s): %s", pageNextURIStr) | ||||
| 			if _, ok := derefdPages[nextURIStr]; ok { | ||||
| 				l.Warnf("self referencing collection page(s): %s", nextURIStr) | ||||
| 				continue stackLoop | ||||
| 			} | ||||
| 
 | ||||
| 			// Mark this collection page as deref'd. | ||||
| 			derefdPages[pageNextURIStr] = struct{}{} | ||||
| 			derefdPages[nextURIStr] = struct{}{} | ||||
| 
 | ||||
| 			// Dereference this next collection page by its IRI. | ||||
| 			collectionPage, err := d.dereferenceCollectionPage(ctx, | ||||
| 				username, | ||||
| 				pageNextURI, | ||||
| 				nextURI, | ||||
| 			) | ||||
| 			if err != nil { | ||||
| 				l.Errorf("error dereferencing collection page %q: %s", pageNextURIStr, err) | ||||
| 				l.Errorf("error dereferencing collection page %q: %s", nextURIStr, err) | ||||
| 				continue stackLoop | ||||
| 			} | ||||
| 
 | ||||
| 			// Set the next collection page. | ||||
| 			current.page = collectionPage | ||||
| 			current.pageURI = pageNextURIStr | ||||
| 			current.pageURI = nextURIStr | ||||
| 			continue pageLoop | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return gtserror.Newf("reached %d descendant iterations for %q", maxIter, statusIRIStr) | ||||
| } | ||||
| 
 | ||||
| // getAttachedStatusCollection is a small utility function to fetch the first page | ||||
| // of an attached activity streams collection from a provided statusable object . | ||||
| func getAttachedStatusCollection(status ap.Statusable) (page ap.CollectionPageable, uri string) { //nolint:gocritic | ||||
| 	// Look for an attached status replies (as collection) | ||||
| 	replies := status.GetActivityStreamsReplies() | ||||
| 	if replies == nil { | ||||
| 		return nil, "" | ||||
| 	} | ||||
| 
 | ||||
| 	// Get the status replies collection | ||||
| 	collection := replies.GetActivityStreamsCollection() | ||||
| 	if collection == nil { | ||||
| 		return nil, "" | ||||
| 	} | ||||
| 
 | ||||
| 	// Get the "first" property of the replies collection | ||||
| 	first := collection.GetActivityStreamsFirst() | ||||
| 	if first == nil { | ||||
| 		return nil, "" | ||||
| 	} | ||||
| 
 | ||||
| 	// Return the first activity stream collection page | ||||
| 	page = first.GetActivityStreamsCollectionPage() | ||||
| 	if page == nil { | ||||
| 		return nil, "" | ||||
| 	} | ||||
| 
 | ||||
| 	if pageID := page.GetJSONLDId(); pageID != nil { | ||||
| 		// By default use collection JSONLD ID | ||||
| 		return page, pageID.Get().String() | ||||
| 	} else if statusID := status.GetJSONLDId(); statusID != nil { | ||||
| 		// Else, if possible use status JSONLD ID | ||||
| 		return page, statusID.Get().String() | ||||
| 	} else { | ||||
| 		// MUST have some kind of ID | ||||
| 		return nil, "" | ||||
| 	} | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue