gotosocial/internal/ap/properties.go

935 lines
30 KiB
Go
Raw Normal View History

// 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"
"time"
"code.superseriousbusiness.org/activity/streams"
"code.superseriousbusiness.org/activity/streams/vocab"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
)
// MustGet performs the given 'Get$Property(with) (T, error)' signature function, panicking on error.
// func MustGet[W, T any](fn func(W) (T, error), with W) T {
// t, err := fn(with)
// if err != nil {
// panicfAt(3, "error getting property on %T: %w", with, err)
// }
// return t
// }
// MustSet performs the given 'Set$Property(with, T) error' signature function, panicking on error.
func MustSet[W, T any](fn func(W, T) error, with W, value T) {
err := fn(with, value)
if err != nil {
panicfAt(3, "error setting property on %T: %w", with, err)
}
}
// AppendSet performs the given 'Append$Property(with, ...T) error' signature function, panicking on error.
// func MustAppend[W, T any](fn func(W, ...T) error, with W, values ...T) {
// err := fn(with, values...)
// if err != nil {
// panicfAt(3, "error appending properties on %T: %w", with, err)
// }
// }
// GetJSONLDId returns the ID of 'with', or nil.
func GetJSONLDId(with WithJSONLDId) *url.URL {
idProp := with.GetJSONLDId()
if idProp == nil || !idProp.IsXMLSchemaAnyURI() {
return nil
}
return idProp.Get()
}
// SetJSONLDId sets the given URL to the JSONLD ID of 'with'.
func SetJSONLDId(with WithJSONLDId, id *url.URL) {
idProp := with.GetJSONLDId()
if idProp == nil {
idProp = streams.NewJSONLDIdProperty()
with.SetJSONLDId(idProp)
}
idProp.SetIRI(id)
}
// SetJSONLDIdStr sets the given string to the JSONLDID of 'with'. Returns error
func SetJSONLDIdStr(with WithJSONLDId, id string) error {
u, err := url.Parse(id)
if err != nil {
return fmt.Errorf("error parsing id url: %w", err)
}
SetJSONLDId(with, u)
return nil
}
// GetTo returns the IRIs contained in the To property of 'with'. Panics on entries with missing ID.
func GetTo(with WithTo) []*url.URL {
toProp := with.GetActivityStreamsTo()
return getIRIs[vocab.ActivityStreamsToPropertyIterator](toProp)
}
// AppendTo appends the given IRIs to the To property of 'with'.
func AppendTo(with WithTo, to ...*url.URL) {
appendIRIs(func() Property[vocab.ActivityStreamsToPropertyIterator] {
toProp := with.GetActivityStreamsTo()
if toProp == nil {
toProp = streams.NewActivityStreamsToProperty()
with.SetActivityStreamsTo(toProp)
}
return toProp
}, to...)
}
// GetCc returns the IRIs contained in the Cc property of 'with'. Panics on entries with missing ID.
func GetCc(with WithCc) []*url.URL {
ccProp := with.GetActivityStreamsCc()
return extractIRIs[vocab.ActivityStreamsCcPropertyIterator](ccProp)
}
// AppendCc appends the given IRIs to the Cc property of 'with'.
func AppendCc(with WithCc, cc ...*url.URL) {
appendIRIs(func() Property[vocab.ActivityStreamsCcPropertyIterator] {
ccProp := with.GetActivityStreamsCc()
if ccProp == nil {
ccProp = streams.NewActivityStreamsCcProperty()
with.SetActivityStreamsCc(ccProp)
}
return ccProp
}, cc...)
}
// GetBcc returns the IRIs contained in the Bcc property of 'with'. Panics on entries with missing ID.
func GetBcc(with WithBcc) []*url.URL {
bccProp := with.GetActivityStreamsBcc()
return extractIRIs[vocab.ActivityStreamsBccPropertyIterator](bccProp)
}
// AppendBcc appends the given IRIs to the Bcc property of 'with'.
func AppendBcc(with WithBcc, bcc ...*url.URL) {
appendIRIs(func() Property[vocab.ActivityStreamsBccPropertyIterator] {
bccProp := with.GetActivityStreamsBcc()
if bccProp == nil {
bccProp = streams.NewActivityStreamsBccProperty()
with.SetActivityStreamsBcc(bccProp)
}
return bccProp
}, bcc...)
}
[bugfix] Update `GetURL` to extract url from Link objects with href (#4249) # Description > If this is a code change, please include a summary of what you've coded, and link to the issue(s) it closes/implements. > > If this is a documentation change, please briefly describe what you've changed and why. This pull request updates our parsing of the `url` property in incoming ActivityPub items to also include Link items, and not just bare URIs. The first discovered url is still used as the *gtsmodel.Account or *gtsmodel.Status `url` property, so this change only really affects our dereference URL anti-spoof check thingy. ~~Should fix https://codeberg.org/superseriousbusiness/gotosocial/issues/4248 but I need to run it and test it myself first to be sure.~~ Fixes https://codeberg.org/superseriousbusiness/gotosocial/issues/4248 ## Checklist Please put an x inside each checkbox to indicate that you've read and followed it: `[ ]` -> `[x]` If this is a documentation change, only the first checkbox must be filled (you can delete the others if you want). - [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md). - [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat. - [x] I/we have not leveraged AI to create the proposed changes. - [x] I/we have performed a self-review of added code. - [x] I/we have written code that is legible and maintainable by others. - [x] I/we have commented the added code, particularly in hard-to-understand areas. - [ ] I/we have made any necessary changes to documentation. - [x] I/we have added tests that cover new code. - [x] I/we have run tests and they pass locally with the changes. - [x] I/we have run `go fmt ./...` and `golangci-lint run`. Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4249 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-06-09 12:32:00 +02:00
// GetURL returns IRIs contained
// in the URL property of 'with'.
func GetURL(with WithURL) []*url.URL {
urlProp := with.GetActivityStreamsUrl()
if urlProp == nil || urlProp.Len() == 0 {
return nil
}
urls := make([]*url.URL, 0, urlProp.Len())
for i := 0; i < urlProp.Len(); i++ {
at := urlProp.At(i)
[bugfix] Update `GetURL` to extract url from Link objects with href (#4249) # Description > If this is a code change, please include a summary of what you've coded, and link to the issue(s) it closes/implements. > > If this is a documentation change, please briefly describe what you've changed and why. This pull request updates our parsing of the `url` property in incoming ActivityPub items to also include Link items, and not just bare URIs. The first discovered url is still used as the *gtsmodel.Account or *gtsmodel.Status `url` property, so this change only really affects our dereference URL anti-spoof check thingy. ~~Should fix https://codeberg.org/superseriousbusiness/gotosocial/issues/4248 but I need to run it and test it myself first to be sure.~~ Fixes https://codeberg.org/superseriousbusiness/gotosocial/issues/4248 ## Checklist Please put an x inside each checkbox to indicate that you've read and followed it: `[ ]` -> `[x]` If this is a documentation change, only the first checkbox must be filled (you can delete the others if you want). - [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md). - [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat. - [x] I/we have not leveraged AI to create the proposed changes. - [x] I/we have performed a self-review of added code. - [x] I/we have written code that is legible and maintainable by others. - [x] I/we have commented the added code, particularly in hard-to-understand areas. - [ ] I/we have made any necessary changes to documentation. - [x] I/we have added tests that cover new code. - [x] I/we have run tests and they pass locally with the changes. - [x] I/we have run `go fmt ./...` and `golangci-lint run`. Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4249 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-06-09 12:32:00 +02:00
// See if it's a plain URI.
if at.IsXMLSchemaAnyURI() {
u := at.GetXMLSchemaAnyURI()
urls = append(urls, u)
[bugfix] Update `GetURL` to extract url from Link objects with href (#4249) # Description > If this is a code change, please include a summary of what you've coded, and link to the issue(s) it closes/implements. > > If this is a documentation change, please briefly describe what you've changed and why. This pull request updates our parsing of the `url` property in incoming ActivityPub items to also include Link items, and not just bare URIs. The first discovered url is still used as the *gtsmodel.Account or *gtsmodel.Status `url` property, so this change only really affects our dereference URL anti-spoof check thingy. ~~Should fix https://codeberg.org/superseriousbusiness/gotosocial/issues/4248 but I need to run it and test it myself first to be sure.~~ Fixes https://codeberg.org/superseriousbusiness/gotosocial/issues/4248 ## Checklist Please put an x inside each checkbox to indicate that you've read and followed it: `[ ]` -> `[x]` If this is a documentation change, only the first checkbox must be filled (you can delete the others if you want). - [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md). - [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat. - [x] I/we have not leveraged AI to create the proposed changes. - [x] I/we have performed a self-review of added code. - [x] I/we have written code that is legible and maintainable by others. - [x] I/we have commented the added code, particularly in hard-to-understand areas. - [ ] I/we have made any necessary changes to documentation. - [x] I/we have added tests that cover new code. - [x] I/we have run tests and they pass locally with the changes. - [x] I/we have run `go fmt ./...` and `golangci-lint run`. Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4249 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-06-09 12:32:00 +02:00
continue
}
// See if it's a Link obj
// with an href property.
if at.IsActivityStreamsLink() {
l := at.GetActivityStreamsLink()
hr := l.GetActivityStreamsHref()
if hr == nil {
// No href.
continue
}
if hr.IsXMLSchemaAnyURI() {
u := hr.Get()
urls = append(urls, u)
} else if hr.IsIRI() {
u := hr.GetIRI()
urls = append(urls, u)
}
}
}
return urls
}
// AppendURL appends the given URLs to the URL property of 'with'.
func AppendURL(with WithURL, url ...*url.URL) {
if len(url) == 0 {
return
}
urlProp := with.GetActivityStreamsUrl()
if urlProp == nil {
urlProp = streams.NewActivityStreamsUrlProperty()
with.SetActivityStreamsUrl(urlProp)
}
for _, u := range url {
urlProp.AppendXMLSchemaAnyURI(u)
}
}
// GetActorIRIs returns the IRIs contained in the Actor property of 'with'.
func GetActorIRIs(with WithActor) []*url.URL {
actorProp := with.GetActivityStreamsActor()
return extractIRIs[vocab.ActivityStreamsActorPropertyIterator](actorProp)
}
// AppendActorIRIs appends the given IRIs to the Actor property of 'with'.
func AppendActorIRIs(with WithActor, actor ...*url.URL) {
appendIRIs(func() Property[vocab.ActivityStreamsActorPropertyIterator] {
actorProp := with.GetActivityStreamsActor()
if actorProp == nil {
actorProp = streams.NewActivityStreamsActorProperty()
with.SetActivityStreamsActor(actorProp)
}
return actorProp
}, actor...)
}
// GetObjectIRIs returns the IRIs contained in the Object property of 'with'.
func GetObjectIRIs(with WithObject) []*url.URL {
objectProp := with.GetActivityStreamsObject()
return extractIRIs[vocab.ActivityStreamsObjectPropertyIterator](objectProp)
}
// AppendObjectIRIs appends the given IRIs to the Object property of 'with'.
func AppendObjectIRIs(with WithObject, object ...*url.URL) {
appendIRIs(func() Property[vocab.ActivityStreamsObjectPropertyIterator] {
objectProp := with.GetActivityStreamsObject()
if objectProp == nil {
objectProp = streams.NewActivityStreamsObjectProperty()
with.SetActivityStreamsObject(objectProp)
}
return objectProp
}, object...)
}
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394) ~~Still WIP!~~ This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0. Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them. As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type. Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation). Migrations tested on both Postgres and SQLite. Co-authored-by: kim <grufwub@gmail.com> Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
// AppendInstrumentIRIs appends the given IRIs to the Instrument property of 'with'.
func AppendInstrumentIRIs(with WithInstrument, instrument ...*url.URL) {
appendIRIs(func() Property[vocab.ActivityStreamsInstrumentPropertyIterator] {
instrumentProp := with.GetActivityStreamsInstrument()
if instrumentProp == nil {
instrumentProp = streams.NewActivityStreamsInstrumentProperty()
with.SetActivityStreamsInstrument(instrumentProp)
}
return instrumentProp
}, instrument...)
}
// GetResultIRIs returns the IRIs contained in the `result` property of 'with'.
func GetResultIRIs(with WithResult) []*url.URL {
resultProp := with.GetActivityStreamsResult()
return extractIRIs(resultProp)
}
// AppendResultIRIs appends the given IRIs to the Result property of 'with'.
func AppendResultIRIs(with WithResult, result ...*url.URL) {
appendIRIs(func() Property[vocab.ActivityStreamsResultPropertyIterator] {
resultProp := with.GetActivityStreamsResult()
if resultProp == nil {
resultProp = streams.NewActivityStreamsResultProperty()
with.SetActivityStreamsResult(resultProp)
}
return resultProp
}, result...)
}
// GetTargetIRIs returns the IRIs contained in the Target property of 'with'.
func GetTargetIRIs(with WithTarget) []*url.URL {
targetProp := with.GetActivityStreamsTarget()
return extractIRIs[vocab.ActivityStreamsTargetPropertyIterator](targetProp)
}
// AppendTargetIRIs appends the given IRIs to the Target property of 'with'.
func AppendTargetIRIs(with WithTarget, target ...*url.URL) {
appendIRIs(func() Property[vocab.ActivityStreamsTargetPropertyIterator] {
targetProp := with.GetActivityStreamsTarget()
if targetProp == nil {
targetProp = streams.NewActivityStreamsTargetProperty()
with.SetActivityStreamsTarget(targetProp)
}
return targetProp
}, target...)
}
// GetAttributedTo returns the IRIs contained in the AttributedTo property of 'with'.
func GetAttributedTo(with WithAttributedTo) []*url.URL {
attribProp := with.GetActivityStreamsAttributedTo()
return extractIRIs[vocab.ActivityStreamsAttributedToPropertyIterator](attribProp)
}
// AppendAttributedTo appends the given IRIs to the AttributedTo property of 'with'.
func AppendAttributedTo(with WithAttributedTo, attribTo ...*url.URL) {
appendIRIs(func() Property[vocab.ActivityStreamsAttributedToPropertyIterator] {
attribProp := with.GetActivityStreamsAttributedTo()
if attribProp == nil {
attribProp = streams.NewActivityStreamsAttributedToProperty()
with.SetActivityStreamsAttributedTo(attribProp)
}
return attribProp
}, attribTo...)
}
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394) ~~Still WIP!~~ This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0. Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them. As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type. Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation). Migrations tested on both Postgres and SQLite. Co-authored-by: kim <grufwub@gmail.com> Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
// GetInteractingObject returns IRIs contained in the interactingObject property of 'with'.
func GetInteractingObject(with WithInteractingObject) []*url.URL {
intObjProp := with.GetGoToSocialInteractingObject()
return getIRIs(intObjProp)
}
// AppendInteractingObject appends the given IRIs to the interactingObject property of 'with'.
func AppendInteractingObject(with WithInteractingObject, interactingObject ...*url.URL) {
appendIRIs(func() Property[vocab.GoToSocialInteractingObjectPropertyIterator] {
intObjProp := with.GetGoToSocialInteractingObject()
if intObjProp == nil {
intObjProp = streams.NewGoToSocialInteractingObjectProperty()
with.SetGoToSocialInteractingObject(intObjProp)
}
return intObjProp
}, interactingObject...)
}
// GetInteractionTarget returns IRIs contained in the interactionTarget property of 'with'.
func GetInteractionTarget(with WithInteractionTarget) []*url.URL {
intTargetProp := with.GetGoToSocialInteractionTarget()
return getIRIs(intTargetProp)
}
// AppendInteractionTarget appends the given IRIs to the interactionTarget property of 'with'.
func AppendInteractionTarget(with WithInteractionTarget, interactionTarget ...*url.URL) {
appendIRIs(func() Property[vocab.GoToSocialInteractionTargetPropertyIterator] {
intTargetProp := with.GetGoToSocialInteractionTarget()
if intTargetProp == nil {
intTargetProp = streams.NewGoToSocialInteractionTargetProperty()
with.SetGoToSocialInteractionTarget(intTargetProp)
}
return intTargetProp
}, interactionTarget...)
}
// GetInReplyTo returns the IRIs contained in the InReplyTo property of 'with'.
func GetInReplyTo(with WithInReplyTo) []*url.URL {
replyProp := with.GetActivityStreamsInReplyTo()
return extractIRIs[vocab.ActivityStreamsInReplyToPropertyIterator](replyProp)
}
// AppendInReplyTo appends the given IRIs to the InReplyTo property of 'with'.
func AppendInReplyTo(with WithInReplyTo, replyTo ...*url.URL) {
appendIRIs(func() Property[vocab.ActivityStreamsInReplyToPropertyIterator] {
replyProp := with.GetActivityStreamsInReplyTo()
if replyProp == nil {
replyProp = streams.NewActivityStreamsInReplyToProperty()
with.SetActivityStreamsInReplyTo(replyProp)
}
return replyProp
}, replyTo...)
}
// GetInbox returns the IRI contained in the Inbox property of 'with'.
func GetInbox(with WithInbox) *url.URL {
inboxProp := with.GetActivityStreamsInbox()
if inboxProp == nil || !inboxProp.IsIRI() {
return nil
}
return inboxProp.GetIRI()
}
// SetInbox sets the given IRI on the Inbox property of 'with'.
func SetInbox(with WithInbox, inbox *url.URL) {
inboxProp := with.GetActivityStreamsInbox()
if inboxProp == nil {
inboxProp = streams.NewActivityStreamsInboxProperty()
with.SetActivityStreamsInbox(inboxProp)
}
inboxProp.SetIRI(inbox)
}
// GetOutbox returns the IRI contained in the Outbox property of 'with'.
func GetOutbox(with WithOutbox) *url.URL {
outboxProp := with.GetActivityStreamsOutbox()
if outboxProp == nil || !outboxProp.IsIRI() {
return nil
}
return outboxProp.GetIRI()
}
// SetOutbox sets the given IRI on the Outbox property of 'with'.
func SetOutbox(with WithOutbox, outbox *url.URL) {
outboxProp := with.GetActivityStreamsOutbox()
if outboxProp == nil {
outboxProp = streams.NewActivityStreamsOutboxProperty()
with.SetActivityStreamsOutbox(outboxProp)
}
outboxProp.SetIRI(outbox)
}
// GetFollowers returns the IRI contained in the Following property of 'with'.
func GetFollowing(with WithFollowing) *url.URL {
followProp := with.GetActivityStreamsFollowing()
if followProp == nil || !followProp.IsIRI() {
return nil
}
return followProp.GetIRI()
}
// SetFollowers sets the given IRI on the Following property of 'with'.
func SetFollowing(with WithFollowing, following *url.URL) {
followProp := with.GetActivityStreamsFollowing()
if followProp == nil {
followProp = streams.NewActivityStreamsFollowingProperty()
with.SetActivityStreamsFollowing(followProp)
}
followProp.SetIRI(following)
}
// GetFollowers returns the IRI contained in the Followers property of 'with'.
func GetFollowers(with WithFollowers) *url.URL {
followProp := with.GetActivityStreamsFollowers()
if followProp == nil || !followProp.IsIRI() {
return nil
}
return followProp.GetIRI()
}
// SetFollowers sets the given IRI on the Followers property of 'with'.
func SetFollowers(with WithFollowers, followers *url.URL) {
followProp := with.GetActivityStreamsFollowers()
if followProp == nil {
followProp = streams.NewActivityStreamsFollowersProperty()
with.SetActivityStreamsFollowers(followProp)
}
followProp.SetIRI(followers)
}
// GetFeatured returns the IRI contained in the Featured property of 'with'.
func GetFeatured(with WithFeatured) *url.URL {
featuredProp := with.GetTootFeatured()
if featuredProp == nil || !featuredProp.IsIRI() {
return nil
}
return featuredProp.GetIRI()
}
// SetFeatured sets the given IRI on the Featured property of 'with'.
func SetFeatured(with WithFeatured, featured *url.URL) {
featuredProp := with.GetTootFeatured()
if featuredProp == nil {
featuredProp = streams.NewTootFeaturedProperty()
with.SetTootFeatured(featuredProp)
}
featuredProp.SetIRI(featured)
}
// GetMovedTo returns the IRI contained in the movedTo property of 'with'.
func GetMovedTo(with WithMovedTo) *url.URL {
movedToProp := with.GetActivityStreamsMovedTo()
if movedToProp == nil || !movedToProp.IsIRI() {
return nil
}
return movedToProp.GetIRI()
}
// SetMovedTo sets the given IRI on the movedTo property of 'with'.
func SetMovedTo(with WithMovedTo, movedTo *url.URL) {
movedToProp := with.GetActivityStreamsMovedTo()
if movedToProp == nil {
movedToProp = streams.NewActivityStreamsMovedToProperty()
with.SetActivityStreamsMovedTo(movedToProp)
}
movedToProp.SetIRI(movedTo)
}
// GetAlsoKnownAs returns the IRI contained in the alsoKnownAs property of 'with'.
func GetAlsoKnownAs(with WithAlsoKnownAs) []*url.URL {
alsoKnownAsProp := with.GetActivityStreamsAlsoKnownAs()
return getIRIs[vocab.ActivityStreamsAlsoKnownAsPropertyIterator](alsoKnownAsProp)
}
// SetAlsoKnownAs sets the given IRIs on the alsoKnownAs property of 'with'.
func SetAlsoKnownAs(with WithAlsoKnownAs, alsoKnownAs []*url.URL) {
appendIRIs(func() Property[vocab.ActivityStreamsAlsoKnownAsPropertyIterator] {
alsoKnownAsProp := with.GetActivityStreamsAlsoKnownAs()
if alsoKnownAsProp == nil {
alsoKnownAsProp = streams.NewActivityStreamsAlsoKnownAsProperty()
with.SetActivityStreamsAlsoKnownAs(alsoKnownAsProp)
}
return alsoKnownAsProp
}, alsoKnownAs...)
}
// GetPublished returns the time contained in the Published property of 'with'.
func GetPublished(with WithPublished) time.Time {
publishProp := with.GetActivityStreamsPublished()
if publishProp == nil || !publishProp.IsXMLSchemaDateTime() {
return time.Time{}
}
return publishProp.Get()
}
// SetPublished sets the given time on the Published property of 'with'.
func SetPublished(with WithPublished, published time.Time) {
publishProp := with.GetActivityStreamsPublished()
if publishProp == nil {
publishProp = streams.NewActivityStreamsPublishedProperty()
with.SetActivityStreamsPublished(publishProp)
}
publishProp.Set(published)
}
[feature] add support for receiving federated status edits (#3597) * add support for extracting Updated field from Statusable implementers * add support for status edits in the database, and update status dereferencer to handle them * remove unused AdditionalInfo{}.CreatedAt * remove unused AdditionalEmojiInfo{}.CreatedAt * update new mention creation to use status.UpdatedAt * remove mention.UpdatedAt, fixes related to NewULIDFromTime() change * add migration to remove Mention{}.UpdatedAt field * add migration to add the StatusEdit{} table * start adding tests, add delete function for status edits * add more of status edit migrations, fill in more of the necessary edit delete functionality * remove unused function * allow generating gotosocial compatible ulid via CLI with `go run ./cmd/gen-ulid` * add StatusEdit{} test models * fix new statusedits sql * use model instead of table name * actually remove the Mention.UpdatedAt field... * fix tests now new models are added, add more status edit DB tests * fix panic wording * add test for deleting status edits * don't automatically set `updated_at` field on updated statuses * flesh out more of the dereferencer status edit tests, ensure updated at field set on outgoing AS statuses * remove media_attachments.updated_at column * fix up more tests, further complete the dereferencer status edit tests * update more status serialization tests not expecting 'updated' AS property * gah!! json serialization tests!! * undo some gtscontext wrapping changes * more serialization test fixing :smiling_face_with_tear: * more test fixing, ensure the edit.status_id field is actually set :facepalm: * fix status edit test * grrr linter * add edited_at field to apimodel status * remove the choice of paging on the timeline public filtered test (otherwise it needs updating every time you add statuses ...) * ensure that status.updated_at always fits chronologically * fix more serialization tests ... * add more code comments * fix envparsing * update swagger file * properly handle media description changes during status edits * slight formatting tweak * code comment
2024-12-05 13:35:07 +00:00
// GetUpdated returns the time contained in the Updated property of 'with'.
func GetUpdated(with WithUpdated) time.Time {
updateProp := with.GetActivityStreamsUpdated()
if updateProp == nil || !updateProp.IsXMLSchemaDateTime() {
return time.Time{}
}
return updateProp.Get()
}
// SetUpdated sets the given time on the Updated property of 'with'.
func SetUpdated(with WithUpdated, updated time.Time) {
updateProp := with.GetActivityStreamsUpdated()
if updateProp == nil {
updateProp = streams.NewActivityStreamsUpdatedProperty()
with.SetActivityStreamsUpdated(updateProp)
}
updateProp.Set(updated)
}
// GetEndTime returns the time contained in the EndTime property of 'with'.
func GetEndTime(with WithEndTime) time.Time {
endTimeProp := with.GetActivityStreamsEndTime()
if endTimeProp == nil || !endTimeProp.IsXMLSchemaDateTime() {
return time.Time{}
}
return endTimeProp.Get()
}
// SetEndTime sets the given time on the EndTime property of 'with'.
func SetEndTime(with WithEndTime, end time.Time) {
endTimeProp := with.GetActivityStreamsEndTime()
if endTimeProp == nil {
endTimeProp = streams.NewActivityStreamsEndTimeProperty()
with.SetActivityStreamsEndTime(endTimeProp)
}
endTimeProp.Set(end)
}
// GetEndTime returns the times contained in the Closed property of 'with'.
func GetClosed(with WithClosed) []time.Time {
closedProp := with.GetActivityStreamsClosed()
if closedProp == nil || closedProp.Len() == 0 {
return nil
}
closed := make([]time.Time, 0, closedProp.Len())
for i := 0; i < closedProp.Len(); i++ {
at := closedProp.At(i)
if at.IsXMLSchemaDateTime() {
t := at.GetXMLSchemaDateTime()
closed = append(closed, t)
}
}
return closed
}
// AppendClosed appends the given times to the Closed property of 'with'.
func AppendClosed(with WithClosed, closed ...time.Time) {
if len(closed) == 0 {
return
}
closedProp := with.GetActivityStreamsClosed()
if closedProp == nil {
closedProp = streams.NewActivityStreamsClosedProperty()
with.SetActivityStreamsClosed(closedProp)
}
for _, closed := range closed {
closedProp.AppendXMLSchemaDateTime(closed)
}
}
// GetVotersCount returns the integer contained in the VotersCount property of 'with', if found.
func GetVotersCount(with WithVotersCount) int {
votersProp := with.GetTootVotersCount()
if votersProp == nil || !votersProp.IsXMLSchemaNonNegativeInteger() {
return 0
}
return votersProp.Get()
}
// SetVotersCount sets the given count on the VotersCount property of 'with'.
func SetVotersCount(with WithVotersCount, count int) {
votersProp := with.GetTootVotersCount()
if votersProp == nil {
votersProp = streams.NewTootVotersCountProperty()
with.SetTootVotersCount(votersProp)
}
votersProp.Set(count)
}
// GetDiscoverable returns the boolean contained in the Discoverable property of 'with'.
//
// Returns default 'false' if property unusable or not set.
func GetDiscoverable(with WithDiscoverable) bool {
discoverProp := with.GetTootDiscoverable()
if discoverProp == nil || !discoverProp.IsXMLSchemaBoolean() {
return false
}
return discoverProp.Get()
}
// SetDiscoverable sets the given boolean on the Discoverable property of 'with'.
func SetDiscoverable(with WithDiscoverable, discoverable bool) {
discoverProp := with.GetTootDiscoverable()
if discoverProp == nil {
discoverProp = streams.NewTootDiscoverableProperty()
with.SetTootDiscoverable(discoverProp)
}
discoverProp.Set(discoverable)
}
// GetManuallyApprovesFollowers returns the boolean contained in the ManuallyApprovesFollowers property of 'with'.
//
// Returns default 'false' if property unusable or not set.
func GetManuallyApprovesFollowers(with WithManuallyApprovesFollowers) bool {
mafProp := with.GetActivityStreamsManuallyApprovesFollowers()
if mafProp == nil || !mafProp.IsXMLSchemaBoolean() {
return false
}
return mafProp.Get()
}
// SetManuallyApprovesFollowers sets the given boolean on the ManuallyApprovesFollowers property of 'with'.
func SetManuallyApprovesFollowers(with WithManuallyApprovesFollowers, manuallyApprovesFollowers bool) {
mafProp := with.GetActivityStreamsManuallyApprovesFollowers()
if mafProp == nil {
mafProp = streams.NewActivityStreamsManuallyApprovesFollowersProperty()
with.SetActivityStreamsManuallyApprovesFollowers(mafProp)
}
mafProp.Set(manuallyApprovesFollowers)
}
[feature] Use `hidesToPublicFromUnauthedWeb` and `hidesCcPublicFromUnauthedWeb` properties for web visibility of statuses (#4315) This pull request implements two new properties on ActivityPub actors: `hidesToPublicFromUnauthedWeb` and `hidesCcPublicFromUnauthedWeb`. As documented, these properties allow actors to signal their preference for whether or not their posts should be hidden from unauthenticated web views (ie., web pages like the GtS frontend, web apps like the Mastodon frontend, web APIs like the Mastodon public timeline API, etc). This allows remote accounts to *opt in* to having their unlisted visibility posts shown in (for example) the replies section of the web view of a GtS thread. In future, we can also use these properties to determine whether we should show boosts of a remote actor's post on a GtS profile, and that sort of thing. In keeping with our stance around privacy by default, GtS assumes `true` for `hidesCcPublicFromUnauthedWeb` if the property is not set on a remote actor, ie., hide unlisted/unlocked posts by default. `hidesToPublicFromUnauthedWeb` is assumed to be `false` if the property is not set on a remote actor, ie., show public posts by default. ~~WIP as I still want to work on the documentation for this a bit.~~ New props are already in the namespace document: https://gotosocial.org/ns Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4315 Reviewed-by: kim <gruf@noreply.codeberg.org> Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-07-09 16:50:25 +02:00
// GetHidesToPublicFromUnauthedWeb returns the boolean contained in the hidesToPublicFromUnauthedWeb property of 'with'.
//
// Returns default 'false' if property unusable or not set.
func GetHidesToPublicFromUnauthedWeb(with WithHidesToPublicFromUnauthedWeb) bool {
hidesProp := with.GetGoToSocialHidesToPublicFromUnauthedWeb()
if hidesProp == nil || !hidesProp.IsXMLSchemaBoolean() {
return false
}
return hidesProp.Get()
}
// SetHidesToPublicFromUnauthedWeb sets the given boolean on the hidesToPublicFromUnauthedWeb property of 'with'.
func SetHidesToPublicFromUnauthedWeb(with WithHidesToPublicFromUnauthedWeb, hidesToPublicFromUnauthedWeb bool) {
hidesProp := with.GetGoToSocialHidesToPublicFromUnauthedWeb()
if hidesProp == nil {
hidesProp = streams.NewGoToSocialHidesToPublicFromUnauthedWebProperty()
with.SetGoToSocialHidesToPublicFromUnauthedWeb(hidesProp)
}
hidesProp.Set(hidesToPublicFromUnauthedWeb)
}
// GetHidesCcPublicFromUnauthedWeb returns the boolean contained in the hidesCcPublicFromUnauthedWeb property of 'with'.
//
// Returns default 'true' if property unusable or not set.
func GetHidesCcPublicFromUnauthedWeb(with WithHidesCcPublicFromUnauthedWeb) bool {
hidesProp := with.GetGoToSocialHidesCcPublicFromUnauthedWeb()
if hidesProp == nil || !hidesProp.IsXMLSchemaBoolean() {
return true
}
return hidesProp.Get()
}
// SetHidesCcPublicFromUnauthedWeb sets the given boolean on the hidesCcPublicFromUnauthedWeb property of 'with'.
func SetHidesCcPublicFromUnauthedWeb(with WithHidesCcPublicFromUnauthedWeb, hidesCcPublicFromUnauthedWeb bool) {
hidesProp := with.GetGoToSocialHidesCcPublicFromUnauthedWeb()
if hidesProp == nil {
hidesProp = streams.NewGoToSocialHidesCcPublicFromUnauthedWebProperty()
with.SetGoToSocialHidesCcPublicFromUnauthedWeb(hidesProp)
}
hidesProp.Set(hidesCcPublicFromUnauthedWeb)
}
// GetApprovedBy returns the URL contained in
// the ApprovedBy property of 'with', if set.
func GetApprovedBy(with WithApprovedBy) *url.URL {
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394) ~~Still WIP!~~ This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0. Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them. As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type. Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation). Migrations tested on both Postgres and SQLite. Co-authored-by: kim <grufwub@gmail.com> Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
abProp := with.GetGoToSocialApprovedBy()
if abProp == nil || !abProp.IsIRI() {
return nil
}
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394) ~~Still WIP!~~ This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0. Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them. As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type. Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation). Migrations tested on both Postgres and SQLite. Co-authored-by: kim <grufwub@gmail.com> Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
return abProp.Get()
}
// SetApprovedBy sets the given url
// on the ApprovedBy property of 'with'.
func SetApprovedBy(with WithApprovedBy, approvedBy *url.URL) {
abProp := with.GetGoToSocialApprovedBy()
if abProp == nil {
abProp = streams.NewGoToSocialApprovedByProperty()
with.SetGoToSocialApprovedBy(abProp)
}
abProp.Set(approvedBy)
}
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394) ~~Still WIP!~~ This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0. Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them. As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type. Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation). Migrations tested on both Postgres and SQLite. Co-authored-by: kim <grufwub@gmail.com> Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
// GetLikeAuthorization returns the URL contained in
// the likeAuthorization property of 'with', if set.
func GetLikeAuthorization(with WithLikeAuthorization) *url.URL {
laProp := with.GetGoToSocialLikeAuthorization()
if laProp == nil || !laProp.IsIRI() {
return nil
}
return laProp.Get()
}
// SetLikeAuthorization sets the given url on
// the 'likeAuthorization' property of 'with'.
func SetLikeAuthorization(with WithLikeAuthorization, likeAuthorization *url.URL) {
laProp := with.GetGoToSocialLikeAuthorization()
if laProp == nil {
laProp = streams.NewGoToSocialLikeAuthorizationProperty()
with.SetGoToSocialLikeAuthorization(laProp)
}
laProp.Set(likeAuthorization)
}
// GetReplyAuthorization returns the URL contained in
// the replyAuthorization property of 'with', if set.
func GetReplyAuthorization(with WithReplyAuthorization) *url.URL {
raProp := with.GetGoToSocialReplyAuthorization()
if raProp == nil || !raProp.IsIRI() {
return nil
}
return raProp.Get()
}
// SetReplyAuthorization sets the given url on
// the 'replyAuthorization' property of 'with'.
func SetReplyAuthorization(with WithReplyAuthorization, replyAuthorization *url.URL) {
raProp := with.GetGoToSocialReplyAuthorization()
if raProp == nil {
raProp = streams.NewGoToSocialReplyAuthorizationProperty()
with.SetGoToSocialReplyAuthorization(raProp)
}
raProp.Set(replyAuthorization)
}
// GetAnnounceAuthorization returns the URL contained in
// the announceAuthorization property of 'with', if set.
func GetAnnounceAuthorization(with WithAnnounceAuthorization) *url.URL {
aaProp := with.GetGoToSocialAnnounceAuthorization()
if aaProp == nil || !aaProp.IsIRI() {
return nil
}
return aaProp.Get()
}
// SetAnnounceAuthorization sets the given url on
// the 'announceAuthorization' property of 'with'.
func SetAnnounceAuthorization(with WithAnnounceAuthorization, announceAuthorization *url.URL) {
aaProp := with.GetGoToSocialAnnounceAuthorization()
if aaProp == nil {
aaProp = streams.NewGoToSocialAnnounceAuthorizationProperty()
with.SetGoToSocialAnnounceAuthorization(aaProp)
}
aaProp.Set(announceAuthorization)
}
// GetMediaType returns the string contained in
// the MediaType property of 'with', if set.
func GetMediaType(with WithMediaType) string {
mtProp := with.GetActivityStreamsMediaType()
if mtProp == nil || !mtProp.IsRFCRfc2045() {
return ""
}
return mtProp.Get()
}
// SetMediaType sets the given string
// on the MediaType property of 'with'.
func SetMediaType(with WithMediaType, mediaType string) {
mtProp := with.GetActivityStreamsMediaType()
if mtProp == nil {
mtProp = streams.NewActivityStreamsMediaTypeProperty()
with.SetActivityStreamsMediaType(mtProp)
}
mtProp.Set(mediaType)
}
// AppendName appends the given name
// vals to the Name property of 'with'.
func AppendName(with WithName, name ...string) {
if len(name) == 0 {
return
}
nameProp := with.GetActivityStreamsName()
if nameProp == nil {
nameProp = streams.NewActivityStreamsNameProperty()
with.SetActivityStreamsName(nameProp)
}
for _, name := range name {
nameProp.AppendXMLSchemaString(name)
}
}
// AppendSummary appends the given summary
// vals to the Summary property of 'with'.
func AppendSummary(with WithSummary, summary ...string) {
if len(summary) == 0 {
return
}
summaryProp := with.GetActivityStreamsSummary()
if summaryProp == nil {
summaryProp = streams.NewActivityStreamsSummaryProperty()
with.SetActivityStreamsSummary(summaryProp)
}
for _, summary := range summary {
summaryProp.AppendXMLSchemaString(summary)
}
}
// SetBlurhash sets the given string
// on the Blurhash property of 'with'.
func SetBlurhash(with WithBlurhash, mediaType string) {
bProp := with.GetTootBlurhash()
if bProp == nil {
bProp = streams.NewTootBlurhashProperty()
with.SetTootBlurhash(bProp)
}
bProp.Set(mediaType)
}
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394) ~~Still WIP!~~ This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0. Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them. As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type. Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation). Migrations tested on both Postgres and SQLite. Co-authored-by: kim <grufwub@gmail.com> Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
// AppendSensitive appends the given sensitive
// boolean to the `sensitive` property of 'with'.
func AppendSensitive(with WithSensitive, sensitive bool) {
sProp := with.GetActivityStreamsSensitive()
if sProp == nil {
sProp = streams.NewActivityStreamsSensitiveProperty()
with.SetActivityStreamsSensitive(sProp)
}
sProp.AppendXMLSchemaBoolean(sensitive)
}
// AppendContent appends the given content
// string to the `content` property of 'with'.
func AppendContent(with WithContent, content string) {
cProp := with.GetActivityStreamsContent()
if cProp == nil {
cProp = streams.NewActivityStreamsContentProperty()
with.SetActivityStreamsContent(cProp)
}
cProp.AppendXMLSchemaString(content)
}
// AppendContentMap appends the given content
// language map to the `content` property of 'with'.
func AppendContentMap(with WithContent, contentMap map[string]string) {
cProp := with.GetActivityStreamsContent()
if cProp == nil {
cProp = streams.NewActivityStreamsContentProperty()
with.SetActivityStreamsContent(cProp)
}
cProp.AppendRDFLangString(contentMap)
}
// SetReplies sets the given replies collection
// to the `replies` property of 'with'.
func SetReplies(with WithReplies, replies vocab.ActivityStreamsCollection) {
rProp := with.GetActivityStreamsReplies()
if rProp == nil {
rProp = streams.NewActivityStreamsRepliesProperty()
with.SetActivityStreamsReplies(rProp)
}
rProp.SetActivityStreamsCollection(replies)
}
// extractIRIs extracts just the AP IRIs from an iterable
// property that may contain types (with IRIs) or just IRIs.
//
// If you know the property contains only IRIs and no types,
// then use getIRIs instead, since it's slightly faster.
func extractIRIs[T TypeOrIRI](prop Property[T]) []*url.URL {
if prop == nil || prop.Len() == 0 {
return nil
}
ids := make([]*url.URL, 0, prop.Len())
for i := 0; i < prop.Len(); i++ {
at := prop.At(i)
if t := at.GetType(); t != nil {
id := GetJSONLDId(t)
if id != nil {
ids = append(ids, id)
continue
}
}
if at.IsIRI() {
id := at.GetIRI()
if id != nil {
ids = append(ids, id)
continue
}
}
}
return ids
}
// getIRIs gets AP IRIs from an iterable property of IRIs.
//
// Types will be ignored; to extract IRIs from an iterable
// that may contain types too, use extractIRIs.
func getIRIs[T WithIRI](prop Property[T]) []*url.URL {
if prop == nil || prop.Len() == 0 {
return nil
}
ids := make([]*url.URL, 0, prop.Len())
for i := 0; i < prop.Len(); i++ {
at := prop.At(i)
if at.IsIRI() {
id := at.GetIRI()
if id != nil {
ids = append(ids, id)
continue
}
}
}
return ids
}
func appendIRIs[T WithIRI](getProp func() Property[T], iri ...*url.URL) {
if len(iri) == 0 {
return
}
prop := getProp()
if prop == nil {
// check outside loop.
panic("prop not set")
}
for _, iri := range iri {
prop.AppendIRI(iri)
}
}
// panicfAt panics with a call to gtserror.NewfAt() with given args (+1 to calldepth).
func panicfAt(calldepth int, msg string, args ...any) {
panic(gtserror.NewfAt(calldepth+1, msg, args...))
}