diff --git a/go.mod b/go.mod index 83d2bc6..3af66f6 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,9 @@ module gitlab.com/mstarongitlab/goap go 1.22.5 -require github.com/piprate/json-gold v0.5.0 - require ( - github.com/datainq/xml-date-time v0.0.0-20170820214645-2292f08baa38 // indirect - github.com/samber/go-type-to-string v1.7.0 // indirect + github.com/datainq/xml-date-time v0.0.0-20170820214645-2292f08baa38 + github.com/piprate/json-gold v0.5.0 ) require ( diff --git a/go.sum b/go.sum index 00b8817..0d9798a 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/samber/go-type-to-string v1.7.0 h1:FiSstaAikHMUSLt5bhVlsvCnD7bbQzC8L0UkkGS3Bj8= -github.com/samber/go-type-to-string v1.7.0/go.mod h1:jpU77vIDoIxkahknKDoEx9C8bQ1ADnh2sotZ8I4QqBU= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= gitlab.com/mstarongitlab/goutils v1.3.0 h1:uuxPHjIU36lyJ8/z4T2xI32zOyh53Xj0Au8K12qkaJ4= diff --git a/old/actor.go b/old/actor.go deleted file mode 100644 index e1e452b..0000000 --- a/old/actor.go +++ /dev/null @@ -1,34 +0,0 @@ -package ap - -type MinimalActor struct { - Id string - Username string - Inbox string -} - -type MastoActor struct { - Id string - Devices MastoDevices - Discoverable MastoDiscoverable - Featured MastoFeatured - FeaturedTags MastoFeaturedTags - Indexable MastoIndexable - Memorial MastoMemorial - Inbox W3Inbox - PublicKey W3SecurityPublicKey - AlsoKnownAs ActivityStreamsAlsoKnownAs - Attachments []ActivityStreamsAttachment - Endpoints []map[string]IdType - Followers ActivityStreamsFollowers - Following ActivityStreamsFollowing - Icon ActivityStreamsIcon - Image ActivityStreamsImage - RestrictedFollow ActivityStreamsRestrictedFollow - Name ActivityStreamsName - Outbox ActivityStreamsOutbox - PrefferedUsername ActivityStreamsPrefferedUsername - Published ActivityStreamsPublished - Summary ActivityStreamsSummary - Tags []ActivityStreamsTag - Url ActivityStreamsUrl -} diff --git a/old/checkHeader.go b/old/checkHeader.go deleted file mode 100644 index 5c09332..0000000 --- a/old/checkHeader.go +++ /dev/null @@ -1,24 +0,0 @@ -package ap - -import "strings" - -// Header used for making requests for AP resources -// Not used yet -const OUTBOUND_REQUEST_HEADER = "application/ld+json, application/json;q=0.9, application/javascript;q=0.5, text/javascript;q=0.5, text/plain;q=0.2, */*;q=0.1" - -var contentHeadersToCheck = []string{ - "application/json", - "application/ld+json", -} - -// Check a given string if it contains any of the content types specified in -// the contentHeadersToCheck slice -// Used for differentiating requests for the ActivityPub version of some data vs frontend version -func ContainsApContentHeader(toCheck string) bool { - for _, h := range contentHeadersToCheck { - if strings.Contains(toCheck, h) { - return true - } - } - return false -} diff --git a/old/common_types.go b/old/common_types.go deleted file mode 100644 index bb500a9..0000000 --- a/old/common_types.go +++ /dev/null @@ -1,18 +0,0 @@ -package ap - -import "net/url" - -type IdType struct { - Id *url.URL -} - -type ValueType[T any] struct { - Value T -} - -type Media struct { - Type url.URL - MediaType *ValueType[string] - Url IdType - Sensitive *ValueType[bool] -} diff --git a/old/constants.go b/old/constants.go deleted file mode 100644 index 4775255..0000000 --- a/old/constants.go +++ /dev/null @@ -1,81 +0,0 @@ -package ap - -const ( - KEY_ID = "@id" // Value of type string - KEY_TYPE = "@type" // Value of type string slice / activitystreams object url slice - KEY_VALUE = "@value" // Could be any type really -) - -const ( - KEY_MASTO_DEVICES = "http://joinmastodon.org/ns#devices" // No idea what this is, but Masto includes it - KEY_MASTO_DISCOVERABLE = "http://joinmastodon.org/ns#discoverable" // iirc this is whether the object can be found by crawlers - KEY_MASTO_FEATURED = "http://joinmastodon.org/ns#featured" // Users tagged? I think - KEY_MASTO_FEATURED_TAGS = "http://joinmastodon.org/ns#featuredTags" // Hashtags included, I think - KEY_MASTO_INDEXABLE = "http://joinmastodon.org/ns#indexable" // Is the object crawlable round 2 - KEY_MASTO_MEMORIAL = "http://joinmastodon.org/ns#memorial" // Account dead and disabled? -) - -const ( - KEY_W3_INBOX = "http://www.w3.org/ns/ldp#inbox" // Where to send activities to - KEY_W3_SECURITY_PUBLICKEY = "https://w3id.org/security#publicKey" // Public key of the account - KEY_W3_SECURITY_OWNER = "https://w3id.org/security#owner" // Owner of the public key - KEY_W3_SECURITY_PUBLICKEYPEM = "https://w3id.org/security#publicKeyPem" // Pem content of the key - KEY_W3_VCARD_ADRESS = "http://www.w3.org/2006/vcard/ns#Address" // Vcard address of an account - KEY_W3_VCARD_BIRTHDAY = "http://www.w3.org/2006/vcard/ns#bday" // Vcard birthday of an account -) - -const ( - KEY_ACTIVITYSTREAMS_ALSOKNOWNAS = "https://www.w3.org/ns/activitystreams#alsoKnownAs" // Lists of usernames? - KEY_ACTIVITYSTREAMS_ATTACHMENTS = "https://www.w3.org/ns/activitystreams#attachment" // Attached elements like emotes and hashtags - KEY_ACTIVITYSTREAMS_NAME = "https://www.w3.org/ns/activitystreams#name" // AP name - KEY_ACTIVITYSTREAMS_ENDPOINTS = "https://www.w3.org/ns/activitystreams#endpoints" // list of assocciated endpoints - KEY_ACTIVITYSTREAMS_SHAREDINBOX = "https://www.w3.org/ns/activitystreams#sharedInbox" // Link to the shared inbox of the server - KEY_ACTIVITYSTREAMS_FOLLOWERS = "https://www.w3.org/ns/activitystreams#followers" // Who is following the account - KEY_ACTIVITYSTREAMS_FOLLOWING = "https://www.w3.org/ns/activitystreams#following" // Who is this account following - KEY_ACTIVITYSTREAMS_ICON = "https://www.w3.org/ns/activitystreams#icon" // User icon - KEY_ACTIVITYSTREAMS_MEDIATYPE = "https://www.w3.org/ns/activitystreams#mediaType" // What type of media is this? Example image/jpeg - KEY_ACTIVITYSTREAMS_URL = "https://www.w3.org/ns/activitystreams#url" // Some url - KEY_ACTIVITYSTREAMS_IMAGE = "https://www.w3.org/ns/activitystreams#image" // Account banner - KEY_ACTIVITYSTREAMS_RESTRICTED_FOLLOW = "https://www.w3.org/ns/activitystreams#manuallyApprovesFollowers" // Does the account manually approve follow requests - KEY_ACTIVITYSTREAMS_OUTBOX = "https://www.w3.org/ns/activitystreams#outbox" // Link to the account's outbox - KEY_ACTIVITYSTREAMS_PREFFEREDUSERNAME = "https://www.w3.org/ns/activitystreams#preferredUsername" // What the shown username is - KEY_ACTIVITYSTREAMS_PUBLISHED = "https://www.w3.org/ns/activitystreams#published" // When an object was created - KEY_ACTIVITYSTREAMS_SUMMARY = "https://www.w3.org/ns/activitystreams#summary" // Summary of an account - KEY_ACTIVITYSTREAMS_TAG = "https://www.w3.org/ns/activitystreams#tag" // A tag, no idea what those are - KEY_ACTIVITYSTREAMS_CC = "https://www.w3.org/ns/activitystreams#cc" // Urls also included in the object - KEY_ACTIVITYSTREAMS_TO = "https://www.w3.org/ns/activitystreams#to" // Urls to send an activity to - KEY_ACTIVITYSTREAMS_OBJECT = "https://www.w3.org/ns/activitystreams#object" // Object url and sometimes value - - // Object types (I think) - // Those are values the object type can have - - KEY_ACTIVITYSTREAMS_ACTOR = "https://www.w3.org/ns/activitystreams#actor" - KEY_ACTIVITYSTREAMS_FOLLOW = "https://www.w3.org/ns/activitystreams#Follow" // Object is an activity of type follow - KEY_ACTIVITYSTREAMS_PERSON = "https://www.w3.org/ns/activitystreams#Person" // Object is of type Person - KEY_ACTIVITYSTREAMS_CREATE = "https://www.w3.org/ns/activitystreams#Create" // Object is an activity of type Create - - KEY_ACTIVITYSTREAMS_OAUTHAUTHORIZATION = "https://www.w3.org/ns/activitystreams#oauthAuthorizationEndpoint" // Endpoint url for oauth login? - KEY_ACTIVITYSTREAMS_OAUTHTOKEN = "https://www.w3.org/ns/activitystreams#oauthTokenEndpoint" // Endpoint url for oauth token verification? - KEY_ACTIVITYSTREAMS_UPLOADMEDIA = "https://www.w3.org/ns/activitystreams#uploadMedia" // Endpoint url to upload media to? -) - -const ( - KEY_SCHEMA_VALUE = "http://schema.org#value" // The value for some field -) - -const ( - KEY_MISSKEY_MKSUMMARY = "https://misskey-hub.net/ns#_misskey_summary" // Misskey specific formatted summary - KEY_MISSKEY_ISCAT = "https://misskey-hub.net/ns#isCat" // Does the account identify as cat? - KEY_FIREFISH_SPEAKASCAT = "https://joinfirefish.org/ns#speakAsCat" // Does the account speak like a cat? -) - -const ( - KEY_LITEPUB_CAPABILITIES = "http://litepub.social/ns#capabilities" - KEY_LITEPUB_OAUTHREGISTRATION = "http://litepub.social/ns#oauthRegistrationEndpoint" -) - -const ( - KEY_MYSTERIOUS_NOINDEX = "_:noindex" - KEY_MYSTERIOUS_BACKGROUNDURL = "_:backgroundUrl" - KEY_MYSTERIOUS_FEATURED = "_:featured" -) diff --git a/old/context.go b/old/context.go deleted file mode 100644 index 71f4a4f..0000000 --- a/old/context.go +++ /dev/null @@ -1,49 +0,0 @@ -package ap - -// Just steal this one from sharkey -// Also use this one for all AP objects for now -// Can make a function to extract context from an expanded object later -// TODO: Consider if such a function is worth it or if it would hinder learning -var allContext = map[string]any{ - "@context": []any{ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - map[string]any{ - "fedibird": "http://fedibird.com/ns#", - "toot": "http://joinmastodon.org/ns#", - "schema": "http://schema.org#", - "misskey": "https://misskey-hub.net/ns#", - "firefish": "https://joinfirefish.org/ns#", - "sharkey": "https://joinsharkey.org/ns#", - "vcard": "http://www.w3.org/2006/vcard/ns#", - - "Key": "sec:Key", - - "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", - "sensitive": "as:sensitive", - "Hashtag": "as:Hashtag", - "quoteUrl": "as:quoteUrl", - - "quoteUri": "fedibird:quoteUri", - - "Emoji": "toot:Emoji", - "featured": "toot:featured", - "discoverable": "toot:discoverable", - - "PropertyValue": "schema:PropertyValue", - "value": "schema:value", - - "_misskey_content": "misskey:_misskey_content", - "_misskey_quote": "misskey:_misskey_quote", - "_misskey_reaction": "misskey:_misskey_reaction", - "_misskey_votes": "misskey:_misskey_votes", - "_misskey_summary": "misskey:_misskey_summary", - "isCat": "misskey:isCat", - - "speakAsCat": "firefish:speakAsCat", - - "backgroundUrl": "sharkey:backgroundUrl", - "listenbrainz": "sharkey:listenbrainz", - }, - }, -} diff --git a/old/explicit_types.go b/old/explicit_types.go deleted file mode 100644 index 49bc2a9..0000000 --- a/old/explicit_types.go +++ /dev/null @@ -1,61 +0,0 @@ -package ap - -import ( - "net/url" - "time" -) - -type ( - ActivityStreamsAlsoKnownAs IdType - ActivityStreamsAttachment struct { - Type *url.URL - Value ValueType[string] - Name ValueType[string] - } - ActivityStreamsSharedInbox IdType - ActivityStreamsFollowers IdType - ActivityStreamsFollowing IdType - ActivityStreamsImage Media - ActivityStreamsIcon Media - ActivityStreamsRestrictedFollow ValueType[bool] - ActivityStreamsName ValueType[string] - ActivityStreamsOutbox IdType - ActivityStreamsPrefferedUsername ValueType[string] - ActivityStreamsPublished struct { - Type string - Value time.Time - } - ActivityStreamsSummary ValueType[string] - ActivityStreamsUrl IdType - // NOTE: Technically can contain a lot more values, but tags are not consistent in what those are. Only consistent one is the type - - ActivityStreamsTag struct { - Type string - } - ActivityStreamsTo IdType - ActivityStreamsCC IdType - - ActivityStreamsActor IdType - // NOTE: Technically, objects can have a LOT of data. I don't care. Treat them as ID type - // Just fetch whatever the ID is later on separately and throw away anything else but the ID - ActivityStreamsObject IdType - ActivityStreamsFollow IdType -) - -type ( - MastoDevices IdType - MastoDiscoverable ValueType[bool] - MastoFeatured IdType - MastoFeaturedTags IdType - MastoIndexable ValueType[bool] - MastoMemorial ValueType[bool] -) - -type ( - W3Inbox IdType - W3SecurityPublicKey struct { - Id *url.URL - Owner *url.URL - KeyPem string - } -) diff --git a/old/parser.go b/old/parser.go deleted file mode 100644 index 36c32ac..0000000 --- a/old/parser.go +++ /dev/null @@ -1,58 +0,0 @@ -package ap - -import ( - "encoding/json" - "fmt" - "net/url" - - "github.com/piprate/json-gold/ld" -) - -type ApThing map[string]any - -type RemoteDocumentLoader struct{} - -var ldOpts = ld.NewJsonLdOptions("") -var ldProcessor = ld.NewJsonLdProcessor() - -// Try and parse a remote ActivityPub object into a more usable form -// Result is a map[string]any where the keys defined in /ap/constants.go should be usable, -// depending on the type of object -// The general approach for fetching an object will be to fetch the main object -// and only store the ID for sub-objects to fetch them later -func ParseFromUrl(u *url.URL) (ApThing, error) { - // TODO: Add custom document parser (copy default implementation) that includes verification - - // Explanation: - // Expansion removes the context from a document (json-ld activitypub data) - // and turns every field into something along the lines of - // "https://example.com/ns#ObjectType": - // This makes it easier to deal with things as they now have a very consistent naming scheme - // See /ap/constants.go for those - - parsed, err := ldProcessor.Expand(u.String(), ldOpts) - if err != nil { - return nil, fmt.Errorf("failed to process remote document: %w", err) - } - if len(parsed) == 0 { - return nil, fmt.Errorf("document has a length of 0") - } - typed, ok := parsed[0].(ApThing) - if !ok { - return nil, fmt.Errorf("couldn't cast data to ApThing") - } - return typed, nil -} - -// Compact an AP object down into a compressed json-ld form -// That compacted form should be accepted by all AP servers -// It also handles context for any fields -// Content should only use keys defined in /ap/constants.go though -// Other things might get lost in translation -func Compact(content map[string]any) ([]byte, error) { - res, err := ldProcessor.Compact(content, allContext, ldOpts) - if err != nil { - return nil, fmt.Errorf("failed to compact data: %w", err) - } - return json.Marshal(res) -} diff --git a/old/type_parsers.go b/old/type_parsers.go deleted file mode 100644 index aea5269..0000000 --- a/old/type_parsers.go +++ /dev/null @@ -1,172 +0,0 @@ -package ap - -import ( - "net/url" - "time" -) - -// Try and parse a value into an IdType -// Returns nil if failed -func TryParseIdType(rawIn any) *IdType { - switch in := rawIn.(type) { - case []any: - if len(in) == 0 { - return nil - } - m, ok := in[0].(map[string]any) - if !ok { - return nil - } - return TryParseIdType(m) - case map[string]any: - vRaw, ok := in[KEY_ID] - if !ok { - return nil - } - v, ok := vRaw.(string) - if !ok { - return nil - } - u, err := url.Parse(v) - if err != nil { - return nil - } - return &IdType{ - Id: u, - } - default: - return nil - } -} - -func TryParseValueType[T any](rawIn any) *ValueType[T] { - switch in := rawIn.(type) { - case map[string]any: - vRaw, ok := in[KEY_ID] - if !ok { - return nil - } - v, ok := vRaw.(T) - if !ok { - return nil - } - return &ValueType[T]{ - Value: v, - } - case []any: - if len(in) == 0 { - return nil - } - v, ok := in[0].(map[string]any) - if !ok { - return nil - } - return TryParseValueType[T](v) - default: - return nil - } -} - -func TryParseActivityStreamsPublicKey(rawIn any) *W3SecurityPublicKey { - switch in := rawIn.(type) { - case map[string]any: - asIdType := TryParseIdType(in) - if asIdType == nil { - return nil - } - ownerType := TryParseIdType(in[KEY_W3_SECURITY_OWNER]) - if ownerType == nil { - return nil - } - keyValue := TryParseValueType[string](in[KEY_W3_SECURITY_PUBLICKEYPEM]) - if keyValue == nil { - return nil - } - return &W3SecurityPublicKey{ - Id: asIdType.Id, - Owner: ownerType.Id, - KeyPem: keyValue.Value, - } - case []any: - if len(in) == 0 { - return nil - } - return TryParseActivityStreamsPublicKey(in[0]) - default: - return nil - } -} - -func TryParseActivityStreamsAttachment(rawIn any) *ActivityStreamsAttachment { - switch in := rawIn.(type) { - case []any: - if len(in) == 0 { - return nil - } - return TryParseActivityStreamsAttachment(in[0]) - case map[string]any: - rawType, ok := in[KEY_TYPE] - if !ok { - return nil - } - strType, ok := rawType.(string) - if !ok { - return nil - } - urlType, err := url.Parse(strType) - if err != nil { - return nil - } - value := TryParseValueType[string](in[KEY_SCHEMA_VALUE]) - if value == nil { - return nil - } - name := TryParseValueType[string](in[KEY_ACTIVITYSTREAMS_NAME]) - if name == nil { - return nil - } - return &ActivityStreamsAttachment{ - Type: urlType, - Name: *name, - Value: *value, - } - default: - return nil - } -} - -func TryParseActivityStreamsPublished(rawIn any) *ActivityStreamsPublished { - switch in := rawIn.(type) { - case []any: - if len(in) == 0 { - return nil - } - return TryParseActivityStreamsPublished(in[0]) - case map[string]any: - rawType, ok := in[KEY_TYPE] - if !ok { - return nil - } - strType, ok := rawType.(string) - if !ok { - return nil - } - value := TryParseValueType[string](in) - tv, err := time.Parse("2006-01-02T04:05:06Z", value.Value) - if err != nil { - return nil - } - return &ActivityStreamsPublished{ - Type: strType, - Value: tv, - } - default: - return nil - } -} - -// NOTE: Since I do not know if tags are consistent with the struct yet, -// This funtion does not do anything yet and should not be used -func TryParseActivityStreamsTag(rawIn any) *ActivityStreamsTag { - return nil -} diff --git a/parser.go b/parser.go new file mode 100644 index 0000000..e245c15 --- /dev/null +++ b/parser.go @@ -0,0 +1,94 @@ +package goap + +import "slices" + +var allInternalParsersExceptBase []UnmarshalFunc = []UnmarshalFunc{ + ParseASActorData, + ParseASAlsoKnownAsData, + ParseASAttachmentsData, + ParseASAttributedToData, + ParseASCCData, + ParseASContentData, + ParseASEndpointsData, + ParseASFollowersData, + ParseASFollowingData, + ParseASHrefData, + ParseASIconData, + ParseASImageData, + ParseASMediaTypeData, + ParseASNameData, + ParseASOutboxData, + ParseASObjectData, + ParseASPreferredNameData, + ParseASPublishedData, + ParseASRestrictedData, + ParseASRepliesData, + ParseASSharedInboxData, + ParseASSummaryData, + ParseASSensitiveData, + ParseASTagData, + ParseASToData, + ParseASUrlData, + ParseASUpdatedData, + + ParseOstatusAtomUriData, + ParseOstatusConversationData, + + ParseW3InboxData, + + ParseW3VcardAddressData, + ParseW3VcardBirthdayData, + + ParseW3SecurityOwnerData, + ParseW3SecurityPublicKeyPemData, + ParseW3SecurityPublicKeyData, + + ParseMastoDevicesData, + ParseMastoDiscoverableData, + ParseMastoFeaturedData, + ParseMastoDiscoverableData, + ParseMastoIndexableData, + ParseMastoMemorialData, + + ParseSchemaValueData, + + ParseMKIsCatData, + ParseMKSummaryData, + + ParseFFSpeakAsCatData, +} + +func chainParse( + raw map[string]any, + start BaseApChain, + parsers ...UnmarshalFunc, +) (BaseApChain, []error) { + var current BaseApChain = start + errors := []error{} + + for _, p := range parsers { + next, err := p(raw, current) + current = next + errors = append(errors, err) + } + + return current, errors +} + +func ParseEmptyBase(raw map[string]any, extraParsers ...UnmarshalFunc) (BaseApChain, []error) { + base := EmptyBaseObject{} + + allParsers := slices.Clone(allInternalParsersExceptBase) + allParsers = append(allParsers, extraParsers...) + return chainParse(raw, &base, allParsers...) +} + +func ParseDefaultBase(raw map[string]any, extraParsers ...UnmarshalFunc) (BaseApChain, []error) { + base, err := UnmarshalBaseObject(raw, nil) + if err != nil { + return base, []error{err} + } + allParsers := slices.Concat(allInternalParsersExceptBase, extraParsers) + res, errs := chainParse(raw, base, allParsers...) + return res, slices.Insert(errs, 0, err) +} diff --git a/tags.go b/tags.go index b39efb2..3b29f79 100644 --- a/tags.go +++ b/tags.go @@ -134,7 +134,7 @@ func parseMentionTag(raw map[string]any) (*TagMention, error) { name := rawName.(*ASNameData) return &TagMention{ TargetId: href.Id, - TargetName: name.Name.Value, + TargetName: name.Value.Value, }, nil } @@ -151,6 +151,6 @@ func parseHashtagTag(raw map[string]any) (*TagHashtag, error) { name := rawName.(*ASNameData) return &TagHashtag{ Url: href.Id, - Name: name.Name.Value, + Name: name.Value.Value, }, nil }