package activitypub import ( "bytes" "encoding/gob" "fmt" "io" "reflect" "strings" "time" "unsafe" "github.com/valyala/fastjson" ) const ( IRIType ActivityVocabularyType = "IRI" ObjectType ActivityVocabularyType = "Object" LinkType ActivityVocabularyType = "Link" ActivityType ActivityVocabularyType = "Activity" IntransitiveActivityType ActivityVocabularyType = "IntransitiveActivity" ActorType ActivityVocabularyType = "Actor" CollectionType ActivityVocabularyType = "Collection" OrderedCollectionType ActivityVocabularyType = "OrderedCollection" CollectionPageType ActivityVocabularyType = "CollectionPage" OrderedCollectionPageType ActivityVocabularyType = "OrderedCollectionPage" // ActivityPub Object Types ArticleType ActivityVocabularyType = "Article" AudioType ActivityVocabularyType = "Audio" DocumentType ActivityVocabularyType = "Document" EventType ActivityVocabularyType = "Event" ImageType ActivityVocabularyType = "Image" NoteType ActivityVocabularyType = "Note" PageType ActivityVocabularyType = "Page" PlaceType ActivityVocabularyType = "Place" ProfileType ActivityVocabularyType = "Profile" RelationshipType ActivityVocabularyType = "Relationship" TombstoneType ActivityVocabularyType = "Tombstone" VideoType ActivityVocabularyType = "Video" // MentionType is a link type for @mentions MentionType ActivityVocabularyType = "Mention" ) var GenericTypes = ActivityVocabularyTypes{ ActivityType, IntransitiveActivityType, ObjectType, ActorType, } var ObjectTypes = ActivityVocabularyTypes{ ArticleType, AudioType, DocumentType, EventType, ImageType, NoteType, PageType, PlaceType, ProfileType, RelationshipType, TombstoneType, VideoType, } type ( // ActivityVocabularyType is the data type for an Activity type object ActivityVocabularyType string // ActivityObject is a subtype of Object that describes some form of action that may happen, // is currently happening, or has already happened ActivityObject interface { // GetID returns the dereferenceable ActivityStreams object id GetID() ID // GetType returns the ActivityStreams type GetType() ActivityVocabularyType } // LinkOrIRI is an interface that Object and Link structs implement, and at the same time // they are kept disjointed LinkOrIRI interface { // GetLink returns the object id in IRI type GetLink() IRI } // ObjectOrLink describes the requirements of an ActivityStreams object ObjectOrLink interface { ActivityObject LinkOrIRI // IsLink shows if current item represents a Link object or an IRI IsLink() bool // IsObject shows if current item represents an ActivityStreams object IsObject() bool // IsCollection shows if the current item represents an ItemCollection IsCollection() bool } // Mapper interface allows external objects to implement their own mechanism for loading information // from an ActivityStreams vocabulary object Mapper interface { // FromActivityStreams maps an ActivityStreams object to another struct representation FromActivityStreams(Item) error } // MimeType is the type for representing MIME types in certain ActivityStreams properties MimeType string ) func (a ActivityVocabularyType) MarshalJSON() ([]byte, error) { if len(a) == 0 { return nil, nil } b := make([]byte, 0) writeStringJSONValue(&b, string(a)) return b, nil } // GobEncode func (a ActivityVocabularyType) GobEncode() ([]byte, error) { return []byte(a), nil } // GobDecode func (a *ActivityVocabularyType) GobDecode(data []byte) error { *a = ActivityVocabularyType(data) return nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. func (a *ActivityVocabularyType) UnmarshalBinary(data []byte) error { return a.GobDecode(data) } // MarshalBinary implements the encoding.BinaryMarshaler interface. func (a ActivityVocabularyType) MarshalBinary() ([]byte, error) { return a.GobEncode() } type Objects interface { Object | Tombstone | Place | Profile | Relationship | Actors | Activities | IntransitiveActivities | Collections | IRI } // Object describes an ActivityPub object of any kind. // It serves as the base type for most of the other kinds of objects defined in the Activity // Vocabulary, including other Core types such as Activity, IntransitiveActivity, Collection and OrderedCollection. type Object struct { // ID provides the globally unique identifier for anActivity Pub Object or Link. ID ID `jsonld:"id,omitempty"` // Type identifies the Activity Pub Object or Link type. Multiple values may be specified. Type ActivityVocabularyType `jsonld:"type,omitempty"` // Name a simple, human-readable, plain-text name for the object. // HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values. Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"` // Attachment identifies a resource attached or related to an object that potentially requires special handling. // The intent is to provide a model that is at least semantically similar to attachments in email. Attachment Item `jsonld:"attachment,omitempty"` // AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors. // For instance, an object might be attributed to the completion of another activity. AttributedTo Item `jsonld:"attributedTo,omitempty"` // Audience identifies one or more entities that represent the total population of entities // for which the object can considered to be relevant. Audience ItemCollection `jsonld:"audience,omitempty"` // Content or textual representation of the Activity Pub Object encoded as a JSON string. // By default, the value of content is HTML. // The mediaType property can be used in the object to indicate a different content type. // (The content MAY be expressed using multiple language-tagged values.) Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"` // Context identifies the context within which the object exists or an activity was performed. // The notion of "context" used is intentionally vague. // The intended function is to serve as a means of grouping objects and activities that share a // common originating context or purpose. An example could be all activities relating to a common project or event. Context Item `jsonld:"context,omitempty"` // MediaType when used on an Object, identifies the MIME media type of the value of the content property. // If not specified, the content property is assumed to contain text/html content. MediaType MimeType `jsonld:"mediaType,omitempty"` // EndTime the date and time describing the actual or expected ending time of the object. // When used with an Activity object, for instance, the endTime property specifies the moment // the activity concluded or is expected to conclude. EndTime time.Time `jsonld:"endTime,omitempty"` // Generator identifies the entity (e.g. an application) that generated the object. Generator Item `jsonld:"generator,omitempty"` // Icon indicates an entity that describes an icon for this object. // The image should have an aspect ratio of one (horizontal) to one (vertical) // and should be suitable for presentation at a small size. Icon Item `jsonld:"icon,omitempty"` // Image indicates an entity that describes an image for this object. // Unlike the icon property, there are no aspect ratio or display size limitations assumed. Image Item `jsonld:"image,omitempty"` // InReplyTo indicates one or more entities for which this object is considered a response. InReplyTo Item `jsonld:"inReplyTo,omitempty"` // Location indicates one or more physical or logical locations associated with the object. Location Item `jsonld:"location,omitempty"` // Preview identifies an entity that provides a preview of this object. Preview Item `jsonld:"preview,omitempty"` // Published the date and time at which the object was published Published time.Time `jsonld:"published,omitempty"` // Replies identifies a Collection containing objects considered to be responses to this object. Replies Item `jsonld:"replies,omitempty"` // StartTime the date and time describing the actual or expected starting time of the object. // When used with an Activity object, for instance, the startTime property specifies // the moment the activity began or is scheduled to begin. StartTime time.Time `jsonld:"startTime,omitempty"` // Summary a natural language summarization of the object encoded as HTML. // *Multiple language tagged summaries may be provided.) Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"` // Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object. // The key difference between attachment and tag is that the former implies association by inclusion, // while the latter implies associated by reference. Tag ItemCollection `jsonld:"tag,omitempty"` // Updated the date and time at which the object was updated Updated time.Time `jsonld:"updated,omitempty"` // URL identifies one or more links to representations of the object URL Item `jsonld:"url,omitempty"` // To identifies an entity considered to be part of the public primary audience of an Activity Pub Object To ItemCollection `jsonld:"to,omitempty"` // Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object. Bto ItemCollection `jsonld:"bto,omitempty"` // CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object. CC ItemCollection `jsonld:"cc,omitempty"` // BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object. BCC ItemCollection `jsonld:"bcc,omitempty"` // Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc, // the duration property indicates the object's approximate duration. // The value must be expressed as an xsd:duration as defined by [ xmlschema11-2], // section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S"). Duration time.Duration `jsonld:"duration,omitempty"` // This is a list of all Like activities with this object as the object property, added as a side effect. // The likes collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges // of an authenticated user or as appropriate when no authentication is given. Likes Item `jsonld:"likes,omitempty"` // This is a list of all Announce activities with this object as the object property, added as a side effect. // The shares collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges // of an authenticated user or as appropriate when no authentication is given. Shares Item `jsonld:"shares,omitempty"` // Source property is intended to convey some sort of source from which the content markup was derived, // as a form of provenance, or to support future editing by clients. // In general, clients do the conversion from source to content, not the other way around. Source Source `jsonld:"source,omitempty"` } // ObjectNew initializes a new Object func ObjectNew(typ ActivityVocabularyType) *Object { if !(ObjectTypes.Contains(typ)) { typ = ObjectType } o := Object{Type: typ} o.Name = NaturalLanguageValuesNew() o.Content = NaturalLanguageValuesNew() return &o } // GetID returns the ID corresponding to the current Object func (o Object) GetID() ID { return o.ID } // GetLink returns the IRI corresponding to the current Object func (o Object) GetLink() IRI { return IRI(o.ID) } // GetType returns the type of the current Object func (o Object) GetType() ActivityVocabularyType { return o.Type } // IsLink validates if currentActivity Pub Object is a Link func (o Object) IsLink() bool { return false } // IsObject validates if currentActivity Pub Object is an Object func (o Object) IsObject() bool { return true } // IsCollection returns false for Object objects func (o Object) IsCollection() bool { return false } // UnmarshalJSON decodes an incoming JSON document into the receiver object. func (o *Object) UnmarshalJSON(data []byte) error { p := fastjson.Parser{} val, err := p.ParseBytes(data) if err != nil { return err } return loadObject(val, o) } // MarshalJSON encodes the receiver object to a JSON document. func (o Object) MarshalJSON() ([]byte, error) { b := make([]byte, 0) write(&b, '{') if writeObjectJSONValue(&b, o) { write(&b, '}') return b, nil } return nil, nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. func (o *Object) UnmarshalBinary(data []byte) error { return o.GobDecode(data) } // MarshalBinary implements the encoding.BinaryMarshaler interface. func (o Object) MarshalBinary() ([]byte, error) { return o.GobEncode() } // GobEncode func (o Object) GobEncode() ([]byte, error) { mm := make(map[string][]byte) hasData, err := mapObjectProperties(mm, &o) if err != nil { return nil, err } if !hasData { return []byte{}, nil } bb := bytes.Buffer{} g := gob.NewEncoder(&bb) if err := g.Encode(mm); err != nil { return nil, err } return bb.Bytes(), nil } // GobDecode func (o *Object) GobDecode(data []byte) error { if len(data) == 0 { return nil } mm, err := gobDecodeObjectAsMap(data) if err != nil { return err } return unmapObjectProperties(mm, o) } func fmtObjectProps(w io.Writer) func(*Object) error { return func(o *Object) error { if len(o.Name) > 0 { n, _ := io.WriteString(w, fmt.Sprintf("%s: [%s]", "name", o.Name)) if n > 0 { io.WriteString(w, ", ") } } if len(o.Summary) > 0 { n, _ := io.WriteString(w, fmt.Sprintf("%s: [%s]", "summary", o.Summary)) if n > 0 { io.WriteString(w, ", ") } } if len(o.Content) > 0 { n, _ := io.WriteString(w, fmt.Sprintf("%s: [%s]", "content", o.Content)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.Attachment) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "attachment", o.Attachment)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.AttributedTo) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "attributedTo", o.AttributedTo)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.Audience) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "audience", o.Audience)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.Context) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "context", o.Context)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.Generator) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "generator", o.Generator)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.Icon) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "icon", o.Icon)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.Image) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "image", o.Image)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.InReplyTo) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "inReplyTo", o.InReplyTo)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.Location) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "location", o.Location)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.Preview) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "preview", o.Preview)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.Replies) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "replies", o.Replies)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.Tag) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "tag", o.Tag)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.URL) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "url", o.URL)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.To) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "to", o.To)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.Bto) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "bto", o.Bto)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.CC) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "cc", o.CC)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.BCC) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "bcc", o.BCC)) if n > 0 { io.WriteString(w, ", ") } } if !o.Published.IsZero() { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "published", o.Published)) if n > 0 { io.WriteString(w, ", ") } } if !o.Updated.IsZero() { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "updated", o.Updated)) if n > 0 { io.WriteString(w, ", ") } } if !o.StartTime.IsZero() { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "startTime", o.StartTime)) if n > 0 { io.WriteString(w, ", ") } } if !o.EndTime.IsZero() { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "endTime", o.EndTime)) if n > 0 { io.WriteString(w, ", ") } } if o.Duration != 0 { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "duration", o.Duration)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.Likes) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "likes", o.Likes)) if n > 0 { io.WriteString(w, ", ") } } if !IsNil(o.Shares) { n, _ := io.WriteString(w, fmt.Sprintf("%s: %s", "shares", o.Shares)) if n > 0 { io.WriteString(w, ", ") } } return nil } } func (o Object) Format(s fmt.State, verb rune) { switch verb { case 's', 'v': io.WriteString(s, fmt.Sprintf("%T[%s] { ", o, o.Type)) fmtObjectProps(s)(&o) io.WriteString(s, " }") } } // Recipients performs recipient de-duplication on the Object's To, Bto, CC and BCC properties func (o *Object) Recipients() ItemCollection { var aud ItemCollection return ItemCollectionDeduplication(&aud, &o.To, &o.Bto, &o.CC, &o.BCC, &o.Audience) } // Clean removes Bto and BCC properties func (o *Object) Clean() { o.BCC = nil o.Bto = nil } type ( // Article represents any kind of multi-paragraph written work. Article = Object // Audio represents an audio document of any kind. Audio = Document // Document represents a document of any kind. Document = Object // Event represents any kind of event. Event = Object // Image An image document of any kind Image = Document // Note represents a short written work typically less than a single paragraph in length. Note = Object // Page represents a Web Page. Page = Document // Video represents a video document of any kind Video = Document ) // UnmarshalJSON decodes an incoming JSON document into the receiver object. func (m *MimeType) UnmarshalJSON(data []byte) error { *m = MimeType(strings.Trim(string(data), "\"")) return nil } // MarshalJSON encodes the receiver object to a JSON document. func (m MimeType) MarshalJSON() ([]byte, error) { if len(m) == 0 { return nil, nil } b := make([]byte, 0) writeStringJSONValue(&b, string(m)) return b, nil } // GobEncode func (m MimeType) GobEncode() ([]byte, error) { if len(m) == 0 { return []byte{}, nil } b := bytes.Buffer{} gg := gob.NewEncoder(&b) if err := gobEncodeStringLikeType(gg, []byte(m)); err != nil { return nil, err } return b.Bytes(), nil } // GobDecode func (m *MimeType) GobDecode(data []byte) error { if len(data) == 0 { // NOTE(marius): this behaviour diverges from vanilla gob package return nil } var bb []byte if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&bb); err != nil { return err } *m = MimeType(bb) return nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. func (m *MimeType) UnmarshalBinary(data []byte) error { return m.GobDecode(data) } // MarshalBinary implements the encoding.BinaryMarshaler interface. func (m MimeType) MarshalBinary() ([]byte, error) { return m.GobEncode() } // ToLink returns a Link pointer to the data in the current Item func ToLink(it LinkOrIRI) (*Link, error) { switch i := it.(type) { case *Link: return i, nil case Link: return &i, nil } return nil, fmt.Errorf("unable to convert %T to %T", it, new(Link)) } // ToObject returns an Object pointer to the data in the current Item // It relies on the fact that all the types in this package have a data layout compatible with Object. func ToObject(it Item) (*Object, error) { switch i := it.(type) { case *Object: return i, nil case Object: return &i, nil case *Place: return (*Object)(unsafe.Pointer(i)), nil case Place: return (*Object)(unsafe.Pointer(&i)), nil case *Profile: return (*Object)(unsafe.Pointer(i)), nil case Profile: return (*Object)(unsafe.Pointer(&i)), nil case *Relationship: return (*Object)(unsafe.Pointer(i)), nil case Relationship: return (*Object)(unsafe.Pointer(&i)), nil case *Tombstone: return (*Object)(unsafe.Pointer(i)), nil case Tombstone: return (*Object)(unsafe.Pointer(&i)), nil case *Actor: return (*Object)(unsafe.Pointer(i)), nil case Actor: return (*Object)(unsafe.Pointer(&i)), nil case *Activity: return (*Object)(unsafe.Pointer(i)), nil case Activity: return (*Object)(unsafe.Pointer(&i)), nil case *IntransitiveActivity: return (*Object)(unsafe.Pointer(i)), nil case IntransitiveActivity: return (*Object)(unsafe.Pointer(&i)), nil case *Question: return (*Object)(unsafe.Pointer(i)), nil case Question: return (*Object)(unsafe.Pointer(&i)), nil case *Collection: return (*Object)(unsafe.Pointer(i)), nil case Collection: return (*Object)(unsafe.Pointer(&i)), nil case *CollectionPage: return (*Object)(unsafe.Pointer(i)), nil case CollectionPage: return (*Object)(unsafe.Pointer(&i)), nil case *OrderedCollection: return (*Object)(unsafe.Pointer(i)), nil case OrderedCollection: return (*Object)(unsafe.Pointer(&i)), nil case *OrderedCollectionPage: return (*Object)(unsafe.Pointer(i)), nil case OrderedCollectionPage: return (*Object)(unsafe.Pointer(&i)), nil default: // NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes typ := reflect.TypeOf(new(Object)) if reflect.TypeOf(it).ConvertibleTo(typ) { if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Object); ok { return i, nil } } } return nil, ErrorInvalidType[Object](it) } // Source is intended to convey some sort of source from which the content markup was derived, // as a form of provenance, or to support future editing by clients. type Source struct { // Content Content NaturalLanguageValues `jsonld:"content"` // MediaType MediaType MimeType `jsonld:"mediaType"` } // GetAPSource func GetAPSource(val *fastjson.Value) Source { s := Source{} if val == nil { return s } if contBytes := val.Get("source", "content").GetStringBytes(); len(contBytes) > 0 { s.Content.UnmarshalJSON(contBytes) } if mimeBytes := val.Get("source", "mediaType").GetStringBytes(); len(mimeBytes) > 0 { s.MediaType.UnmarshalJSON(mimeBytes) } return s } // UnmarshalJSON decodes an incoming JSON document into the receiver object. func (s *Source) UnmarshalJSON(data []byte) error { p := fastjson.Parser{} val, err := p.ParseBytes(data) if err != nil { return err } *s = GetAPSource(val) return nil } // MarshalJSON encodes the receiver object to a JSON document. func (s Source) MarshalJSON() ([]byte, error) { b := make([]byte, 0) empty := true write(&b, '{') if len(s.MediaType) > 0 { if v, err := s.MediaType.MarshalJSON(); err == nil && len(v) > 0 { empty = !writeJSONProp(&b, "mediaType", v) } } if len(s.Content) > 0 { empty = !writeNaturalLanguageJSONProp(&b, "content", s.Content) } if !empty { write(&b, '}') return b, nil } return nil, nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. func (s *Source) UnmarshalBinary(data []byte) error { return s.GobDecode(data) } // MarshalBinary implements the encoding.BinaryMarshaler interface. func (s Source) MarshalBinary() ([]byte, error) { return s.GobEncode() } // GobDecode func (s *Source) GobDecode(data []byte) error { if len(data) == 0 { return nil } mm := make(map[string][]byte) g := gob.NewDecoder(bytes.NewReader(data)) if err := g.Decode(&mm); err != nil { return err } if raw, ok := mm["mediaType"]; ok { if err := s.MediaType.GobDecode(raw); err != nil { return err } } if raw, ok := mm["content"]; ok { if err := s.Content.GobDecode(raw); err != nil { return err } } return nil } // GobEncode func (s Source) GobEncode() ([]byte, error) { var ( mm = make(map[string][]byte) err error hasData bool ) if len(s.MediaType) > 0 { if mm["mediaType"], err = s.MediaType.GobEncode(); err != nil { return nil, err } hasData = true } if len(s.Content) > 0 { if mm["content"], err = s.Content.GobEncode(); err != nil { return nil, err } hasData = true } if !hasData { return []byte{}, nil } bb := bytes.Buffer{} g := gob.NewEncoder(&bb) if err := g.Encode(mm); err != nil { return nil, err } return bb.Bytes(), nil } // Equals verifies if our receiver Object is equals with the "with" Object func (o Object) Equals(with Item) bool { if with.IsCollection() { return false } if withID := with.GetID(); len(withID) > 0 && withID != o.ID { return false } if withType := with.GetType(); len(withType) > 0 && withType != o.Type { return false } if with.IsLink() && !with.GetLink().Equals(o.GetLink(), false) { return false } result := true err := OnObject(with, func(w *Object) error { if len(w.Name) > 0 { if !w.Name.Equals(o.Name) { result = false return nil } } if len(w.Summary) > 0 { if !w.Summary.Equals(o.Summary) { result = false return nil } } if len(w.Content) > 0 { if !w.Content.Equals(o.Content) { result = false return nil } } if w.Attachment != nil { if !ItemsEqual(o.Attachment, w.Attachment) { result = false return nil } } if w.AttributedTo != nil { if !ItemsEqual(o.AttributedTo, w.AttributedTo) { result = false return nil } } if w.Audience != nil { if !ItemsEqual(o.Audience, w.Audience) { result = false return nil } } if w.Context != nil { if !ItemsEqual(o.Context, w.Context) { result = false return nil } } if w.Generator != nil { if !ItemsEqual(o.Generator, w.Generator) { result = false return nil } } if w.Icon != nil { if !ItemsEqual(o.Icon, w.Icon) { result = false return nil } } if w.Image != nil { if !ItemsEqual(o.Image, w.Image) { result = false return nil } } if w.InReplyTo != nil { if !ItemsEqual(o.InReplyTo, w.InReplyTo) { result = false return nil } } if w.Location != nil { if !ItemsEqual(o.Location, w.Location) { result = false return nil } } if w.Preview != nil { if !ItemsEqual(o.Preview, w.Preview) { result = false return nil } } if w.Replies != nil { if !ItemsEqual(o.Replies, w.Replies) { result = false return nil } } if w.Tag != nil { if !ItemsEqual(o.Tag, w.Tag) { result = false return nil } } if w.URL != nil { if o.URL == nil { result = false return nil } if !w.URL.GetLink().Equals(o.URL.GetLink(), false) { result = false return nil } } if w.To != nil { if !ItemsEqual(o.To, w.To) { result = false return nil } } if w.Bto != nil { if !ItemsEqual(o.Bto, w.Bto) { result = false return nil } } if w.CC != nil { if !ItemsEqual(o.CC, w.CC) { result = false return nil } } if w.BCC != nil { if !ItemsEqual(o.BCC, w.BCC) { result = false return nil } } if !w.Published.IsZero() { if !w.Published.Equal(o.Published) { result = false return nil } } if !w.Updated.IsZero() { if !w.Updated.Equal(o.Updated) { result = false return nil } } if !w.StartTime.IsZero() { if !w.StartTime.Equal(o.StartTime) { result = false return nil } } if !w.EndTime.IsZero() { if !w.EndTime.Equal(o.EndTime) { result = false return nil } } if w.Duration != 0 { if w.Duration != o.Duration { result = false return nil } } if w.Likes != nil { if !ItemsEqual(o.Likes, w.Likes) { result = false return nil } } if w.Shares != nil { if !ItemsEqual(o.Shares, w.Shares) { result = false return nil } } return nil }) if err != nil { result = false } return result }