From cc4926684bd99b9f6cc591004660e0624c7e0fd3 Mon Sep 17 00:00:00 2001 From: Xeronith Date: Wed, 30 Nov 2022 09:13:31 +0330 Subject: [PATCH] feat(activitypub): :technologist: protocol objects and helpers --- greataped/app/activitypub/activity.go | 16 ++++++++ greataped/app/activitypub/actor.go | 49 +++++++++++++++++++++++ greataped/app/activitypub/collection.go | 32 +++++++++++++++ greataped/app/activitypub/create.go | 10 +++++ greataped/app/activitypub/followers.go | 21 ++++++++++ greataped/app/activitypub/note.go | 53 +++++++++++++++++++++++++ greataped/app/activitypub/object.go | 18 +++++++++ greataped/app/activitypub/outbox.go | 21 ++++++++++ greataped/app/activitypub/types.go | 13 ++++++ greataped/app/activitypub/webfinger.go | 38 ++++++++++++++++++ 10 files changed, 271 insertions(+) create mode 100644 greataped/app/activitypub/activity.go create mode 100644 greataped/app/activitypub/actor.go create mode 100644 greataped/app/activitypub/collection.go create mode 100644 greataped/app/activitypub/create.go create mode 100644 greataped/app/activitypub/followers.go create mode 100644 greataped/app/activitypub/note.go create mode 100644 greataped/app/activitypub/object.go create mode 100644 greataped/app/activitypub/outbox.go create mode 100644 greataped/app/activitypub/types.go create mode 100644 greataped/app/activitypub/webfinger.go diff --git a/greataped/app/activitypub/activity.go b/greataped/app/activitypub/activity.go new file mode 100644 index 0000000..9900d7d --- /dev/null +++ b/greataped/app/activitypub/activity.go @@ -0,0 +1,16 @@ +package activitypub + +import "time" + +type Activity struct { + Context interface{} `json:"@context"` + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Actor string `json:"actor,omitempty"` + Object interface{} `json:"object,omitempty"` + From string `json:"from,omitempty"` + To interface{} `json:"to,omitempty"` + InReplyTo string `json:"inReplyTo,omitempty"` + Content string `json:"content,omitempty"` + Published time.Time `json:"published,omitempty"` +} diff --git a/greataped/app/activitypub/actor.go b/greataped/app/activitypub/actor.go new file mode 100644 index 0000000..9869eb4 --- /dev/null +++ b/greataped/app/activitypub/actor.go @@ -0,0 +1,49 @@ +package activitypub + +import ( + "encoding/json" + "time" +) + +type Actor struct { + Context []interface{} `json:"@context"` + Followers string `json:"followers"` + Following string `json:"following"` + ID string `json:"id"` + Type string `json:"type"` + PreferredUsername string `json:"preferredUsername"` + Inbox string `json:"inbox"` + Outbox string `json:"outbox"` + Playlists string `json:"playlists"` + Name string `json:"name"` + PublicKey PublicKey `json:"publicKey"` + Url string `json:"url"` + Summary string `json:"summary"` + Published time.Time `json:"published"` + Icon Icon `json:"icon,omitempty"` + Image Icon `json:"image,omitempty"` +} + +type Icon struct { + Height int64 `json:"height,omitempty"` + MediaType string `json:"mediaType,omitempty"` + Type string `json:"type,omitempty"` + URL string `json:"url,omitempty"` + Width int64 `json:"width,omitempty"` +} + +type PublicKey struct { + ID string `json:"id"` + Owner string `json:"owner"` + PublicKeyPem string `json:"publicKeyPem"` +} + +func UnmarshalActor(data []byte) (Actor, error) { + var r Actor + err := json.Unmarshal(data, &r) + return r, err +} + +func (r *Actor) Marshal() ([]byte, error) { + return json.Marshal(r) +} diff --git a/greataped/app/activitypub/collection.go b/greataped/app/activitypub/collection.go new file mode 100644 index 0000000..8439283 --- /dev/null +++ b/greataped/app/activitypub/collection.go @@ -0,0 +1,32 @@ +package activitypub + +import "encoding/json" + +type OrderedCollection struct { + Context string `json:"@context"` + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + TotalItems int `json:"totalItems"` + OrderedItems interface{} `json:"orderedItems,omitempty"` + First string `json:"first,omitempty"` +} + +func NewOrderedCollection(id string, items interface{}, length int) *OrderedCollection { + return &OrderedCollection{ + Context: ActivityStreams, + ID: id, + Type: TypeOrderedCollection, + TotalItems: length, + OrderedItems: items, + } +} + +func UnmarshalOrderedCollection(data []byte) (OrderedCollection, error) { + var o OrderedCollection + err := json.Unmarshal(data, &o) + return o, err +} + +func (o *OrderedCollection) Marshal() ([]byte, error) { + return json.Marshal(o) +} diff --git a/greataped/app/activitypub/create.go b/greataped/app/activitypub/create.go new file mode 100644 index 0000000..81eb798 --- /dev/null +++ b/greataped/app/activitypub/create.go @@ -0,0 +1,10 @@ +package activitypub + +type Create struct { + Context string `json:"@context"` + Type string `json:"type"` + Id string `json:"id"` + To []string `json:"to"` + Actor string `json:"actor"` + Object interface{} `json:"object"` +} diff --git a/greataped/app/activitypub/followers.go b/greataped/app/activitypub/followers.go new file mode 100644 index 0000000..9a41471 --- /dev/null +++ b/greataped/app/activitypub/followers.go @@ -0,0 +1,21 @@ +package activitypub + +import "encoding/json" + +type Followers struct { + Context string `json:"@context"` + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + TotalItems int `json:"totalItems"` + OrderedItems interface{} `json:"orderedItems,omitempty"` +} + +func UnmarshalFollowers(data []byte) (Followers, error) { + var o Followers + err := json.Unmarshal(data, &o) + return o, err +} + +func (o *Followers) Marshal() ([]byte, error) { + return json.Marshal(o) +} diff --git a/greataped/app/activitypub/note.go b/greataped/app/activitypub/note.go new file mode 100644 index 0000000..b391b6d --- /dev/null +++ b/greataped/app/activitypub/note.go @@ -0,0 +1,53 @@ +package activitypub + +import ( + "encoding/json" + "fmt" + "time" +) + +type Note struct { + Context string `json:"@context" validate:"activitystream"` + Id string `json:"id,omitempty"` + Type string `json:"type"` + To []string `json:"to"` + AttributedTo string `json:"attributedTo"` + InReplyTo string `json:"inReplyTo,omitempty"` + Content string `json:"content"` +} + +func NewNote(from, to, content string) *Note { + return &Note{ + Context: ActivityStreams, + To: []string{to}, + Content: content, + Type: TypeNote, + AttributedTo: from, + } +} + +func NewPublicNote(from, content string) *Note { + return NewNote(from, Public, content) +} + +func (note *Note) Wrap(username, publicUrl, uniqueIdentifier string) *Activity { + return &Activity{ + Context: ActivityStreams, + Type: TypeCreate, + ID: fmt.Sprintf("%s/u/%s/posts/%s", publicUrl, username, uniqueIdentifier), + To: note.To, + Actor: fmt.Sprintf("%s/u/%s", publicUrl, username), + Published: time.Now(), + Object: note, + } +} + +func UnmarshalNote(data []byte) (Note, error) { + var note Note + err := json.Unmarshal(data, ¬e) + return note, err +} + +func (note *Note) Marshal() ([]byte, error) { + return json.Marshal(note) +} diff --git a/greataped/app/activitypub/object.go b/greataped/app/activitypub/object.go new file mode 100644 index 0000000..e71b231 --- /dev/null +++ b/greataped/app/activitypub/object.go @@ -0,0 +1,18 @@ +package activitypub + +import "encoding/json" + +type Object struct { + Context interface{} `json:"@context"` + Type string `json:"type"` +} + +func UnmarshalObject(data []byte) (Object, error) { + var object Object + err := json.Unmarshal(data, &object) + return object, err +} + +func (object *Object) Marshal() ([]byte, error) { + return json.Marshal(object) +} diff --git a/greataped/app/activitypub/outbox.go b/greataped/app/activitypub/outbox.go new file mode 100644 index 0000000..72765e6 --- /dev/null +++ b/greataped/app/activitypub/outbox.go @@ -0,0 +1,21 @@ +package activitypub + +import "encoding/json" + +type Outbox struct { + Context string `json:"@context"` + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + TotalItems int `json:"totalItems"` + OrderedItems interface{} `json:"orderedItems,omitempty"` +} + +func UnmarshalOutbox(data []byte) (Outbox, error) { + var o Outbox + err := json.Unmarshal(data, &o) + return o, err +} + +func (o *Outbox) Marshal() ([]byte, error) { + return json.Marshal(o) +} diff --git a/greataped/app/activitypub/types.go b/greataped/app/activitypub/types.go new file mode 100644 index 0000000..ebdc8ec --- /dev/null +++ b/greataped/app/activitypub/types.go @@ -0,0 +1,13 @@ +package activitypub + +const ( + ActivityStreams = "https://www.w3.org/ns/activitystreams" + + TypeCreate = "Create" + TypeFollow = "Follow" + TypeAccept = "Accept" + TypeNote = "Note" + TypeOrderedCollection = "OrderedCollection" + + Public = ActivityStreams + "#Public" +) diff --git a/greataped/app/activitypub/webfinger.go b/greataped/app/activitypub/webfinger.go new file mode 100644 index 0000000..a9afe9b --- /dev/null +++ b/greataped/app/activitypub/webfinger.go @@ -0,0 +1,38 @@ +package activitypub + +import "encoding/json" + +func UnmarshalWebfinger(data []byte) (Webfinger, error) { + var r Webfinger + err := json.Unmarshal(data, &r) + return r, err +} + +func (r *Webfinger) Marshal() ([]byte, error) { + return json.Marshal(r) +} + +type Webfinger struct { + Aliases []string `json:"aliases"` + Links []Link `json:"links"` + Subject string `json:"subject"` +} + +type Link struct { + Href *string `json:"href,omitempty"` + Rel string `json:"rel"` + Type *string `json:"type,omitempty"` + Template *string `json:"template,omitempty"` +} + +func (webfinger *Webfinger) Self() string { + self := "" + for _, link := range webfinger.Links { + if link.Rel == "self" && link.Type != nil && *link.Type == "application/activity+json" { + self = *link.Href + break + } + } + + return self +}