Compare commits

...

10 commits

Author SHA1 Message Date
5aec50990e Add CHANGELOG 2024-08-30 11:56:01 +00:00
Melody
d83260e277 And implement the new keys 2024-08-30 13:49:41 +02:00
Melody
ce173bbb74 And new keys 2024-08-30 13:48:13 +02:00
Melody
0281bc9428 More example files 2024-08-30 13:47:37 +02:00
Melody
85547bbd2c Merge remote-tracking branch 'origin/main' 2024-08-28 19:37:29 +02:00
Melody
61bba41c05 Docs 2024-08-28 19:35:54 +02:00
7bad3f2997 Add LICENSE 2024-08-28 17:18:00 +00:00
Melody
2f74543ca1 Meow
- No more BaseObject with Id and Type included, moved those into the
nsUndefined namespace
- Refactored the full parser funcs into two slightly distinct ones:
  - One that takes in raw bytes and one that takes in a map[string]any
2024-08-28 19:14:23 +02:00
Melody
33a82e544b Add MarshalChain func 2024-08-26 17:19:45 +02:00
Melody
b6071c50ec Delete old stuff, added full parser 2024-08-26 17:17:08 +02:00
26 changed files with 887 additions and 617 deletions

14
CHANGELOG Normal file
View file

@ -0,0 +1,14 @@
- v1.1.0:
- More attributes added:
- Activitystreams#first
- Activitystreams#next
- Activitystreams#items
- Activitystreams#partOf
- Activitystreams#inReplyTo
- Activitystreams#quoteUrl
- Ostatus#inReplyToAtomUri
- Misskey#mk_quote
- Fedibird#quoteUri
- Bug fixes:
- A few Attributes were returning the wrong struct due to copy paste mistakes
- v1.0.0: Release

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 mStar
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -16,48 +16,6 @@ type BaseApChain interface {
// It is expected that, on success, the function removes its key from the raw map
type UnmarshalFunc func(map[string]any, BaseApChain) (BaseApChain, error)
// The minimum data every AP object has
type BaseObject struct {
Id string
Type string
}
func (b *BaseObject) GetSelfOrBase() (BaseApChain, bool) {
return b, false
}
func (b *BaseObject) MarshalToMap() map[string]any {
return map[string]any{
KEY_ID: b.Id,
KEY_TYPE: b.Type,
}
}
func UnmarshalBaseObject(raw map[string]any, _ BaseApChain) (BaseApChain, error) {
rawId, ok := raw[KEY_ID]
if !ok {
return nil, NoRequiredFieldError{KEY_ID}
}
id, ok := rawId.(string)
if !ok {
return nil, BadFieldValueError[string]{KEY_ID, rawId, ""}
}
rawObjType, ok := raw[KEY_TYPE]
if !ok {
return nil, NoRequiredFieldError{KEY_TYPE}
}
objType, ok := rawObjType.([]string)
if !ok {
return nil, BadFieldValueError[[]string]{KEY_TYPE, rawObjType, []string{}}
}
return &BaseObject{
Id: id,
Type: objType[0],
}, nil
}
type EmptyBaseObject struct{}
func (e *EmptyBaseObject) GetSelfOrBase() (BaseApChain, bool) {

View file

@ -56,6 +56,8 @@ const (
KEY_ACTIVITYSTREAMS_TO = "https://www.w3.org/ns/activitystreams#to" // Urls to send an activity to
KEY_ACTIVITYSTREAMS_URL = "https://www.w3.org/ns/activitystreams#url" // Some url
KEY_ACTIVITYSTREAMS_UPDATED = "https://www.w3.org/ns/activitystreams#updated" // When the content was last updated
KEY_ACTIVITYSTREAMS_INREPLYTO = "https://www.w3.org/ns/activitystreams#inReplyTo" // What the object is replying to
KEY_ACTIVITYSTREAMS_QUOTEURL = "https://www.w3.org/ns/activitystreams#quoteUrl" // Url to the object being quoted
// Object types (I think)
// Those are values the object type can have
@ -93,9 +95,11 @@ const (
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_MISSKEY_MKQUOTE = "https://misskey-hub.net/ns#_misskey_quote" // Misskey specific quote field
KEY_FIREFISH_SPEAKASCAT = "https://joinfirefish.org/ns#speakAsCat" // Does the account speak like a cat?
)
// No idea what either of those two do or are used for
const (
KEY_LITEPUB_CAPABILITIES = "http://litepub.social/ns#capabilities"
KEY_LITEPUB_OAUTHREGISTRATION = "http://litepub.social/ns#oauthRegistrationEndpoint"
@ -107,8 +111,9 @@ const (
// NOTE: ostatus.org seems to be redirecting to some weird scam(?) page
const (
KEY_OSTATUS_ATOMURI = "http://ostatus.org#atomUri"
KEY_OSTATUS_CONVERSATION = "http://ostatus.org#conversation"
KEY_OSTATUS_ATOMURI = "http://ostatus.org#atomUri" // Same as @id I think
KEY_OSTATUS_CONVERSATION = "http://ostatus.org#conversation" // Similar to inReplyTo
KEY_OSTATUS_INREPLYTOATOMURI = "http://ostatus.org#inReplyToAtomUri" // Same as InReplyTo, but with an atom uri as target
)
const (
@ -116,3 +121,7 @@ const (
KEY_MYSTERIOUS_BACKGROUNDURL = "_:backgroundUrl"
KEY_MYSTERIOUS_FEATURED = "_:featured"
)
const (
KEY_FEDIBIRD_QUOTEURI = "http://fedibird.com/ns#quoteUri"
)

View file

@ -0,0 +1,122 @@
[
{
"@id": "https://activitypub.academy/users/fagusia_aseod/statuses/113046100718699502/replies",
"@type": [
"https://www.w3.org/ns/activitystreams#Collection"
],
"https://www.w3.org/ns/activitystreams#first": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod/statuses/113046100718699502/replies?page=true",
"@type": [
"https://www.w3.org/ns/activitystreams#CollectionPage"
],
"https://www.w3.org/ns/activitystreams#items": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod/statuses/113046106230737452",
"@type": [
"https://www.w3.org/ns/activitystreams#Note"
],
"http://ostatus.org#atomUri": [
{
"@value": "https://activitypub.academy/users/fagusia_aseod/statuses/113046106230737452"
}
],
"http://ostatus.org#conversation": [
{
"@value": "tag:activitypub.academy,2024-08-29:objectId=186920:objectType=Conversation"
}
],
"http://ostatus.org#inReplyToAtomUri": [
{
"@value": "https://activitypub.academy/users/fagusia_aseod/statuses/113046100718699502"
}
],
"https://www.w3.org/ns/activitystreams#attachment": [],
"https://www.w3.org/ns/activitystreams#attributedTo": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod"
}
],
"https://www.w3.org/ns/activitystreams#cc": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod/followers"
}
],
"https://www.w3.org/ns/activitystreams#content": [
{
"@value": "\u003cp\u003ereply\u003c/p\u003e"
},
{
"@language": "en",
"@value": "\u003cp\u003ereply\u003c/p\u003e"
}
],
"https://www.w3.org/ns/activitystreams#inReplyTo": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod/statuses/113046100718699502"
}
],
"https://www.w3.org/ns/activitystreams#published": [
{
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
"@value": "2024-08-29T15:51:29Z"
}
],
"https://www.w3.org/ns/activitystreams#replies": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod/statuses/113046106230737452/replies",
"@type": [
"https://www.w3.org/ns/activitystreams#Collection"
],
"https://www.w3.org/ns/activitystreams#first": [
{
"@type": [
"https://www.w3.org/ns/activitystreams#CollectionPage"
],
"https://www.w3.org/ns/activitystreams#items": [],
"https://www.w3.org/ns/activitystreams#next": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod/statuses/113046106230737452/replies?only_other_accounts=true\u0026page=true"
}
],
"https://www.w3.org/ns/activitystreams#partOf": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod/statuses/113046106230737452/replies"
}
]
}
]
}
],
"https://www.w3.org/ns/activitystreams#sensitive": [
{
"@value": false
}
],
"https://www.w3.org/ns/activitystreams#tag": [],
"https://www.w3.org/ns/activitystreams#to": [
{
"@id": "https://www.w3.org/ns/activitystreams#Public"
}
],
"https://www.w3.org/ns/activitystreams#url": [
{
"@id": "https://activitypub.academy/@fagusia_aseod/113046106230737452"
}
]
}
],
"https://www.w3.org/ns/activitystreams#next": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod/statuses/113046100718699502/replies?only_other_accounts=true\u0026page=true"
}
],
"https://www.w3.org/ns/activitystreams#partOf": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod/statuses/113046100718699502/replies"
}
]
}
]
}
]

96
examples/masto_reply.json Normal file
View file

@ -0,0 +1,96 @@
[
{
"@id": "https://activitypub.academy/users/fagusia_aseod/statuses/113049754774052158",
"@type": [
"https://www.w3.org/ns/activitystreams#Note"
],
"http://ostatus.org#atomUri": [
{
"@value": "https://activitypub.academy/users/fagusia_aseod/statuses/113049754774052158"
}
],
"http://ostatus.org#conversation": [
{
"@value": "tag:activitypub.academy,2024-08-30:objectId=186955:objectType=Conversation"
}
],
"http://ostatus.org#inReplyToAtomUri": [
{
"@value": "https://activitypub.academy/users/fagusia_aseod/statuses/113049753895761634"
}
],
"https://www.w3.org/ns/activitystreams#attachment": [],
"https://www.w3.org/ns/activitystreams#attributedTo": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod"
}
],
"https://www.w3.org/ns/activitystreams#cc": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod/followers"
}
],
"https://www.w3.org/ns/activitystreams#content": [
{
"@value": "\u003cp\u003ereply\u003c/p\u003e"
},
{
"@language": "en",
"@value": "\u003cp\u003ereply\u003c/p\u003e"
}
],
"https://www.w3.org/ns/activitystreams#inReplyTo": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod/statuses/113049753895761634"
}
],
"https://www.w3.org/ns/activitystreams#published": [
{
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
"@value": "2024-08-30T07:19:21Z"
}
],
"https://www.w3.org/ns/activitystreams#replies": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod/statuses/113049754774052158/replies",
"@type": [
"https://www.w3.org/ns/activitystreams#Collection"
],
"https://www.w3.org/ns/activitystreams#first": [
{
"@type": [
"https://www.w3.org/ns/activitystreams#CollectionPage"
],
"https://www.w3.org/ns/activitystreams#items": [],
"https://www.w3.org/ns/activitystreams#next": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod/statuses/113049754774052158/replies?only_other_accounts=true\u0026page=true"
}
],
"https://www.w3.org/ns/activitystreams#partOf": [
{
"@id": "https://activitypub.academy/users/fagusia_aseod/statuses/113049754774052158/replies"
}
]
}
]
}
],
"https://www.w3.org/ns/activitystreams#sensitive": [
{
"@value": false
}
],
"https://www.w3.org/ns/activitystreams#tag": [],
"https://www.w3.org/ns/activitystreams#to": [
{
"@id": "https://www.w3.org/ns/activitystreams#Public"
}
],
"https://www.w3.org/ns/activitystreams#url": [
{
"@id": "https://activitypub.academy/@fagusia_aseod/113049754774052158"
}
]
}
]

75
examples/mk_quote.json Normal file
View file

@ -0,0 +1,75 @@
[
{
"@id": "https://fedi.catboy.agency/notes/9xk3lj95g43b02t8",
"@type": [
"https://www.w3.org/ns/activitystreams#Note"
],
"http://fedibird.com/ns#quoteUri": [
{
"@value": "https://activitypub.academy/users/fagusia_aseod/statuses/113050350412353987"
}
],
"https://misskey-hub.net/ns#_misskey_content": [
{
"@value": "quote test"
}
],
"https://misskey-hub.net/ns#_misskey_quote": [
{
"@value": "https://activitypub.academy/users/fagusia_aseod/statuses/113050350412353987"
}
],
"https://www.w3.org/ns/activitystreams#attachment": [],
"https://www.w3.org/ns/activitystreams#attributedTo": [
{
"@id": "https://fedi.catboy.agency/users/9ltetvu2n34t01lj"
}
],
"https://www.w3.org/ns/activitystreams#cc": [
{
"@id": "https://fedi.catboy.agency/users/9ltetvu2n34t01lj/followers"
}
],
"https://www.w3.org/ns/activitystreams#content": [
{
"@value": "\u003cp\u003e\u003cspan\u003equote test\u003cbr\u003e\u003cbr\u003eRE: \u003c/span\u003e\u003ca href=\"https://activitypub.academy/users/fagusia_aseod/statuses/113050350412353987\"\u003ehttps://activitypub.academy/users/fagusia_aseod/statuses/113050350412353987\u003c/a\u003e\u003c/p\u003e"
}
],
"https://www.w3.org/ns/activitystreams#published": [
{
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
"@value": "2024-08-30T09:51:01.049Z"
}
],
"https://www.w3.org/ns/activitystreams#quoteUrl": [
{
"@value": "https://activitypub.academy/users/fagusia_aseod/statuses/113050350412353987"
}
],
"https://www.w3.org/ns/activitystreams#sensitive": [
{
"@value": false
}
],
"https://www.w3.org/ns/activitystreams#source": [
{
"https://www.w3.org/ns/activitystreams#content": [
{
"@value": "quote test"
}
],
"https://www.w3.org/ns/activitystreams#mediaType": [
{
"@value": "text/x.misskeymarkdown"
}
]
}
],
"https://www.w3.org/ns/activitystreams#tag": [],
"https://www.w3.org/ns/activitystreams#to": [
{
"@id": "https://www.w3.org/ns/activitystreams#Public"
}
]
}
]

View file

@ -0,0 +1,43 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"ostatus": "http://ostatus.org#",
"atomUri": "ostatus:atomUri",
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
"conversation": "ostatus:conversation",
"sensitive": "as:sensitive",
"toot": "http://joinmastodon.org/ns#",
"votersCount": "toot:votersCount"
}
],
"id": "https://activitypub.academy/users/fagusia_aseod/statuses/113049754774052158",
"type": "Note",
"summary": null,
"inReplyTo": "https://activitypub.academy/users/fagusia_aseod/statuses/113049753895761634",
"published": "2024-08-30T07:19:21Z",
"url": "https://activitypub.academy/@fagusia_aseod/113049754774052158",
"attributedTo": "https://activitypub.academy/users/fagusia_aseod",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"cc": ["https://activitypub.academy/users/fagusia_aseod/followers"],
"sensitive": false,
"atomUri": "https://activitypub.academy/users/fagusia_aseod/statuses/113049754774052158",
"inReplyToAtomUri": "https://activitypub.academy/users/fagusia_aseod/statuses/113049753895761634",
"conversation": "tag:activitypub.academy,2024-08-30:objectId=186955:objectType=Conversation",
"content": "<p>reply</p>",
"contentMap": {
"en": "<p>reply</p>"
},
"attachment": [],
"tag": [],
"replies": {
"id": "https://activitypub.academy/users/fagusia_aseod/statuses/113049754774052158/replies",
"type": "Collection",
"first": {
"type": "CollectionPage",
"next": "https://activitypub.academy/users/fagusia_aseod/statuses/113049754774052158/replies?only_other_accounts=true&page=true",
"partOf": "https://activitypub.academy/users/fagusia_aseod/statuses/113049754774052158/replies",
"items": []
}
}
}

View file

@ -0,0 +1,54 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"Key": "sec:Key",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"sensitive": "as:sensitive",
"Hashtag": "as:Hashtag",
"quoteUrl": "as:quoteUrl",
"fedibird": "http://fedibird.com/ns#",
"quoteUri": "fedibird:quoteUri",
"toot": "http://joinmastodon.org/ns#",
"Emoji": "toot:Emoji",
"featured": "toot:featured",
"discoverable": "toot:discoverable",
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value",
"misskey": "https://misskey-hub.net/ns#",
"_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",
"firefish": "https://joinfirefish.org/ns#",
"speakAsCat": "firefish:speakAsCat",
"sharkey": "https://joinsharkey.org/ns#",
"backgroundUrl": "sharkey:backgroundUrl",
"listenbrainz": "sharkey:listenbrainz",
"vcard": "http://www.w3.org/2006/vcard/ns#"
}
],
"id": "https://fedi.catboy.agency/notes/9xk3lj95g43b02t8",
"type": "Note",
"attributedTo": "https://fedi.catboy.agency/users/9ltetvu2n34t01lj",
"content": "<p><span>quote test<br><br>RE: </span><a href=\"https://activitypub.academy/users/fagusia_aseod/statuses/113050350412353987\">https://activitypub.academy/users/fagusia_aseod/statuses/113050350412353987</a></p>",
"_misskey_content": "quote test",
"source": {
"content": "quote test",
"mediaType": "text/x.misskeymarkdown"
},
"_misskey_quote": "https://activitypub.academy/users/fagusia_aseod/statuses/113050350412353987",
"quoteUrl": "https://activitypub.academy/users/fagusia_aseod/statuses/113050350412353987",
"quoteUri": "https://activitypub.academy/users/fagusia_aseod/statuses/113050350412353987",
"published": "2024-08-30T09:51:01.049Z",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"cc": ["https://fedi.catboy.agency/users/9ltetvu2n34t01lj/followers"],
"inReplyTo": null,
"attachment": [],
"sensitive": false,
"tag": []
}

6
go.mod
View file

@ -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 (

2
go.sum
View file

@ -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=

View file

@ -327,7 +327,7 @@ func ParseASFollowersData(raw map[string]any, next BaseApChain) (BaseApChain, er
if err != nil {
return nil, err
}
return &ASAttributedToData{FullIdType: *id}, nil
return &ASFollowersData{FullIdType: *id}, nil
}
type ASFollowingData struct {
@ -347,7 +347,7 @@ func ParseASFollowingData(raw map[string]any, next BaseApChain) (BaseApChain, er
if err != nil {
return nil, err
}
return &ASAttributedToData{FullIdType: *id}, nil
return &ASFollowingData{FullIdType: *id}, nil
}
type ASHrefData struct {
@ -367,7 +367,7 @@ func ParseASHrefData(raw map[string]any, next BaseApChain) (BaseApChain, error)
if err != nil {
return nil, err
}
return &ASAttributedToData{FullIdType: *id}, nil
return &ASHrefData{FullIdType: *id}, nil
}
type ASIconData struct {
@ -503,7 +503,7 @@ func (attributedtodata *ASOutboxData) GetSelfOrBase() (BaseApChain, bool) {
}
func (attributedtodata *ASOutboxData) MarshalToMap() map[string]any {
return attributedtodata.FullIdType.MarshalToMapWithName(KEY_ACTIVITYSTREAMS_HREF)
return attributedtodata.FullIdType.MarshalToMapWithName(KEY_ACTIVITYSTREAMS_OUTBOX)
}
func ParseASOutboxData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
@ -515,23 +515,29 @@ func ParseASOutboxData(raw map[string]any, next BaseApChain) (BaseApChain, error
}
type ASObjectData struct {
FullIdType
Next BaseApChain
// Unparsed objects. Please parse yourself. Go doesn't like the recursion if the parser for this attribute was calling Unmarshal
Objects []map[string]any
}
func (object *ASObjectData) GetSelfOrBase() (BaseApChain, bool) {
return object.FullIdType.Next, true
return object.Next, true
}
func (object *ASObjectData) MarshalToMap() map[string]any {
return object.FullIdType.MarshalToMapWithName(KEY_ACTIVITYSTREAMS_OBJECT)
return appendWithKey(object.Next.MarshalToMap(), KEY_ACTIVITYSTREAMS_OBJECT, object.Objects)
}
func ParseASObjectData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
id, err := ParseIdTypeWithName(raw, next, KEY_ACTIVITYSTREAMS_OBJECT)
if err != nil {
return nil, err
rawData1, ok := raw[KEY_ACTIVITYSTREAMS_OBJECT]
if !ok {
return nil, NoRequiredFieldError{KEY_ACTIVITYSTREAMS_OBJECT}
}
return &ASObjectData{FullIdType: *id}, nil
data, ok := rawData1.([]map[string]any)
if !ok {
return nil, BadFieldValueError[[]map[string]any]{KEY_ACTIVITYSTREAMS_OBJECT, rawData1, data}
}
return &ASObjectData{Next: next, Objects: data}, nil
}
type ASPreferredNameData struct {
@ -801,7 +807,7 @@ func (object *ASUrlData) GetSelfOrBase() (BaseApChain, bool) {
}
func (object *ASUrlData) MarshalToMap() map[string]any {
return object.FullIdType.MarshalToMapWithName(KEY_ACTIVITYSTREAMS_SHAREDINBOX)
return object.FullIdType.MarshalToMapWithName(KEY_ACTIVITYSTREAMS_URL)
}
func ParseASUrlData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
@ -858,3 +864,148 @@ func ParseASUpdatedData(raw map[string]any, next BaseApChain) (BaseApChain, erro
Timestamp: t,
}, nil
}
type ASFirstData struct {
Next BaseApChain
// Technically the object is decodable into BaseApChain as well
// but Go doesn't like the potential recursive call
// So uhh, you have to do it yourself
Objects map[string]any
}
func (a *ASFirstData) GetSelfOrBase() (BaseApChain, bool) {
return a.Next, true
}
func (a *ASFirstData) MarshalToMap() map[string]any {
return appendWithKey(
a.Next.MarshalToMap(),
KEY_ACTIVITYSTREAMS_FIRST,
[]map[string]any{a.Objects},
)
}
func ParseASFirstData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
rawData, ok := raw[KEY_ACTIVITYSTREAMS_FIRST]
if !ok {
return nil, NoRequiredFieldError{KEY_ACTIVITYSTREAMS_FIRST}
}
data, ok := rawData.([]map[string]any)
if !ok {
return nil, BadFieldValueError[[]map[string]any]{KEY_ACTIVITYSTREAMS_FIRST, rawData, data}
}
if len(data) != 1 {
return nil, BadFieldArrayLengthError{KEY_ACTIVITYSTREAMS_FIRST, 1, len(data)}
}
delete(raw, KEY_ACTIVITYSTREAMS_FIRST)
return &ASFirstData{
Next: next,
Objects: data[0],
}, nil
}
type ASItemsData struct {
Next BaseApChain
// Items are all AP objects, but can't preparse since Go doesn't like the potential recursion
Items []map[string]any
}
func (a *ASItemsData) GetSelfOrBase() (BaseApChain, bool) {
return a.Next, true
}
func (a *ASItemsData) MarshalToMap() map[string]any {
return appendWithKey(a.Next.MarshalToMap(), KEY_ACTIVITYSTREAMS_ITEMS, a.Items)
}
func ParseASItemsData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
rawData, ok := raw[KEY_ACTIVITYSTREAMS_ITEMS]
if !ok {
return nil, NoRequiredFieldError{KEY_ACTIVITYSTREAMS_ITEMS}
}
data, ok := rawData.([]map[string]any)
if !ok {
return nil, BadFieldValueError[[]map[string]any]{KEY_ACTIVITYSTREAMS_ITEMS, rawData, data}
}
return &ASItemsData{Next: next, Items: data}, nil
}
type ASNextData struct {
FullIdType
}
func (object *ASNextData) GetSelfOrBase() (BaseApChain, bool) {
return object.FullIdType.Next, true
}
func (object *ASNextData) MarshalToMap() map[string]any {
return object.FullIdType.MarshalToMapWithName(KEY_ACTIVITYSTREAMS_NEXT)
}
func ParseASNextData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
id, err := ParseIdTypeWithName(raw, next, KEY_ACTIVITYSTREAMS_NEXT)
if err != nil {
return nil, err
}
return &ASNextData{FullIdType: *id}, nil
}
type ASPartOfData struct {
FullIdType
}
func (object *ASPartOfData) GetSelfOrBase() (BaseApChain, bool) {
return object.FullIdType.Next, true
}
func (object *ASPartOfData) MarshalToMap() map[string]any {
return object.FullIdType.MarshalToMapWithName(KEY_ACTIVITYSTREAMS_PARTOF)
}
func ParseASPartOfData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
id, err := ParseIdTypeWithName(raw, next, KEY_ACTIVITYSTREAMS_PARTOF)
if err != nil {
return nil, err
}
return &ASPartOfData{FullIdType: *id}, nil
}
type ASInReplyTo struct {
FullIdType
}
func (a *ASInReplyTo) GetSelfOrBase() (BaseApChain, bool) {
return a.Next, true
}
func (a *ASInReplyTo) MarshalToMap() map[string]any {
return a.MarshalToMapWithName(KEY_ACTIVITYSTREAMS_INREPLYTO)
}
func ParseASInReplyToData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
id, err := ParseIdTypeWithName(raw, next, KEY_ACTIVITYSTREAMS_INREPLYTO)
if err != nil {
return nil, err
}
return &ASInReplyTo{*id}, nil
}
type ASQuoteUrlData struct {
FullValueType[string]
}
func (namedata *ASQuoteUrlData) GetSelfOrBase() (BaseApChain, bool) {
return namedata.Next, true
}
func (namedata *ASQuoteUrlData) MarshalToMap() map[string]any {
return namedata.MarshalToMapWithName(KEY_ACTIVITYSTREAMS_QUOTEURL)
}
func ParseASQuoteUrlData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
tmp, err := ParseValueTypeWithName[string](raw, next, KEY_ACTIVITYSTREAMS_QUOTEURL)
if err != nil {
return nil, err
}
return &ASQuoteUrlData{*tmp}, nil
}

21
nsFedibird.go Normal file
View file

@ -0,0 +1,21 @@
package goap
type FedibirdQuoteUriData struct {
FullValueType[string]
}
func (f *FedibirdQuoteUriData) GetSelfOrBase() (BaseApChain, bool) {
return f.Next, true
}
func (f *FedibirdQuoteUriData) MarshalToMap() map[string]any {
return f.MarshalToMapWithName(KEY_FEDIBIRD_QUOTEURI)
}
func ParseFedibirdQuoteUriData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
v, err := ParseValueTypeWithName[string](raw, next, KEY_FEDIBIRD_QUOTEURI)
if err != nil {
return nil, err
}
return &FedibirdQuoteUriData{*v}, nil
}

View file

@ -61,3 +61,23 @@ func ParseFFSpeakAsCatData(raw map[string]any, next BaseApChain) (BaseApChain, e
}
return &FFSpeakAsCatData{*tmp}, nil
}
type MKQuoteData struct {
FullValueType[string]
}
func (misskeysummarydata *MKQuoteData) GetSelfOrBase() (BaseApChain, bool) {
return misskeysummarydata.Next, true
}
func (misskeysummarydata *MKQuoteData) MarshalToMap() map[string]any {
return misskeysummarydata.MarshalToMapWithName(KEY_MISSKEY_MKQUOTE)
}
func ParseMKQuoteData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
tmp, err := ParseValueTypeWithName[string](raw, next, KEY_MISSKEY_MKQUOTE)
if err != nil {
return nil, err
}
return &MKQuoteData{*tmp}, nil
}

View file

@ -1,8 +1,7 @@
package goap
type OstatusAtomUriData struct {
Next BaseApChain
Uri ValueValue[string]
FullValueType[string]
}
func (atomurivalue *OstatusAtomUriData) GetSelfOrBase() (BaseApChain, bool) {
@ -10,43 +9,19 @@ func (atomurivalue *OstatusAtomUriData) GetSelfOrBase() (BaseApChain, bool) {
}
func (atomurivalue *OstatusAtomUriData) MarshalToMap() map[string]any {
return appendWithKey(
atomurivalue.Next.MarshalToMap(),
KEY_OSTATUS_ATOMURI,
[]map[string]any{atomurivalue.Uri.Marshal()},
)
return atomurivalue.MarshalToMapWithName(KEY_OSTATUS_ATOMURI)
}
func ParseOstatusAtomUriData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
rawData1, ok := raw[KEY_OSTATUS_ATOMURI]
if !ok {
return nil, NoRequiredFieldError{KEY_OSTATUS_ATOMURI}
}
data1, ok := rawData1.([]map[string]any)
if !ok {
return nil, BadFieldValueError[[]map[string]any]{
KEY_OSTATUS_ATOMURI,
rawData1,
data1,
}
}
if len(data1) != 1 {
return nil, BadFieldArrayLengthError{KEY_OSTATUS_ATOMURI, 1, len(data1)}
}
tmp, err := ParseValueValue[string](data1[0])
v, err := ParseValueTypeWithName[string](raw, next, KEY_OSTATUS_ATOMURI)
if err != nil {
return nil, err
}
delete(raw, KEY_OSTATUS_ATOMURI)
return &OstatusAtomUriData{
Next: next,
Uri: *tmp,
}, nil
return &OstatusAtomUriData{*v}, nil
}
type OstatusConversationData struct {
Next BaseApChain
Uri ValueValue[string]
FullValueType[string]
}
func (atomurivalue *OstatusConversationData) GetSelfOrBase() (BaseApChain, bool) {
@ -54,36 +29,33 @@ func (atomurivalue *OstatusConversationData) GetSelfOrBase() (BaseApChain, bool)
}
func (atomurivalue *OstatusConversationData) MarshalToMap() map[string]any {
return appendWithKey(
atomurivalue.Next.MarshalToMap(),
KEY_OSTATUS_CONVERSATION,
[]map[string]any{atomurivalue.Uri.Marshal()},
)
return atomurivalue.MarshalToMapWithName(KEY_OSTATUS_CONVERSATION)
}
func ParseOstatusConversationData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
rawData1, ok := raw[KEY_OSTATUS_CONVERSATION]
if !ok {
return nil, NoRequiredFieldError{KEY_OSTATUS_CONVERSATION}
}
data1, ok := rawData1.([]map[string]any)
if !ok {
return nil, BadFieldValueError[[]map[string]any]{
KEY_OSTATUS_CONVERSATION,
rawData1,
data1,
}
}
if len(data1) != 1 {
return nil, BadFieldArrayLengthError{KEY_OSTATUS_CONVERSATION, 1, len(data1)}
}
tmp, err := ParseValueValue[string](data1[0])
v, err := ParseValueTypeWithName[string](raw, next, KEY_OSTATUS_CONVERSATION)
if err != nil {
return nil, err
}
delete(raw, KEY_OSTATUS_CONVERSATION)
return &OstatusConversationData{
Next: next,
Uri: *tmp,
}, nil
return &OstatusConversationData{*v}, nil
}
type OstatusReplyToAtomUriData struct {
FullValueType[string]
}
func (a *OstatusReplyToAtomUriData) GetSelfOrBase() (BaseApChain, bool) {
return a.Next, true
}
func (a *OstatusReplyToAtomUriData) MarshalToMap() map[string]any {
return a.MarshalToMapWithName(KEY_OSTATUS_INREPLYTOATOMURI)
}
func ParseOstatusInReplyToAtomUriData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
v, err := ParseValueTypeWithName[string](raw, next, KEY_OSTATUS_INREPLYTOATOMURI)
if err != nil {
return nil, err
}
return &OstatusReplyToAtomUriData{*v}, nil
}

58
nsUndefined.go Normal file
View file

@ -0,0 +1,58 @@
package goap
type UDIdData struct {
Next BaseApChain
Id string
}
func (u *UDIdData) GetSelfOrBase() (BaseApChain, bool) {
return u.Next, true
}
func (b *UDIdData) MarshalToMap() map[string]any {
return appendWithKey(b.Next.MarshalToMap(), "KEY_ID", b.Id)
}
func ParseUDIdData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
rawId, ok := raw[KEY_ID]
if !ok {
return nil, NoRequiredFieldError{KEY_ID}
}
id, ok := rawId.(string)
if !ok {
return nil, BadFieldValueError[string]{KEY_ID, rawId, ""}
}
delete(raw, KEY_ID)
return &UDIdData{
Next: next,
Id: id,
}, nil
}
type UDTypeData struct {
Next BaseApChain
Type string
}
func (u *UDTypeData) GetSelfOrBase() (BaseApChain, bool) {
return u.Next, true
}
func (u *UDTypeData) MarshalToMap() map[string]any {
return appendWithKey(u.Next.MarshalToMap(), KEY_TYPE, []any{u.Type})
}
func ParseUDTypeData(raw map[string]any, next BaseApChain) (BaseApChain, error) {
rawObjType, ok := raw[KEY_TYPE]
if !ok {
return nil, NoRequiredFieldError{KEY_TYPE}
}
objType, ok := rawObjType.([]string)
if !ok {
return nil, BadFieldValueError[[]string]{KEY_TYPE, rawObjType, []string{}}
}
if len(objType) != 1 {
return nil, BadFieldArrayLengthError{KEY_TYPE, 1, len(objType)}
}
return &UDTypeData{Next: next, Type: objType[0]}, nil
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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]
}

View file

@ -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"
)

View file

@ -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",
},
},
}

View file

@ -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
}
)

View file

@ -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": <Insert data for that object here>
// 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)
}

View file

@ -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
}

157
parser.go Normal file
View file

@ -0,0 +1,157 @@
package goap
import (
"fmt"
"slices"
"github.com/piprate/json-gold/ld"
)
// Internal so that others don't mess with this and potentially accidentally overwrite it, breaking everything
var allInternalParsersExceptBase []UnmarshalFunc = []UnmarshalFunc{
ParseUDIdData,
ParseUDTypeData,
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,
ParseASFirstData,
ParseASNextData,
ParseASItemsData,
ParseASPartOfData,
ParseASInReplyToData,
ParseASQuoteUrlData,
ParseOstatusAtomUriData,
ParseOstatusConversationData,
ParseOstatusInReplyToAtomUriData,
ParseW3InboxData,
ParseW3VcardAddressData,
ParseW3VcardBirthdayData,
ParseW3SecurityOwnerData,
ParseW3SecurityPublicKeyPemData,
ParseW3SecurityPublicKeyData,
ParseMastoDevicesData,
ParseMastoDiscoverableData,
ParseMastoFeaturedData,
ParseMastoDiscoverableData,
ParseMastoIndexableData,
ParseMastoMemorialData,
ParseSchemaValueData,
ParseMKIsCatData,
ParseMKSummaryData,
ParseMKQuoteData,
ParseFFSpeakAsCatData,
ParseFedibirdQuoteUriData,
}
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
}
// Unmarshal raw data into an Activitypub object in the form of a recursive BaseApChain
// Each element corresponds to one attribute the object contained
// If ldOptions or processor are nil, the following defaults are used:
// - ldOptions: `ld.NewJsonLdOptions("")`
// - processor: `ld.NewJsonLdProcessor()`
// Returns an ActivityPub object and a list of errors produced by the parser functions
// Not each function has to produce an error. Those will have nil as error value
// Those errors (or nils) come in the order of the parser functions
// which can be inspected in the `allInternalParsersExceptBase` internal variable
func Unmarshal(
raw []byte,
ldOptions *ld.JsonLdOptions,
processor *ld.JsonLdProcessor,
extraParsers ...UnmarshalFunc,
) (BaseApChain, []error) {
if ldOptions == nil {
ldOptions = ld.NewJsonLdOptions("")
}
if processor == nil {
processor = ld.NewJsonLdProcessor()
}
rawData, err := processor.Expand(raw, ldOptions)
if err != nil {
return nil, []error{err}
}
data, ok := rawData[0].(map[string]any)
if !ok {
return nil, []error{fmt.Errorf("failed to cast expand result into map[string]any")}
}
return UnmarshalPreprocessed(data, extraParsers...)
}
// Unmarshal a preproccessed object. Preproccessed meaning being first parsed as json and then expanded by json-gold
// Otherwise the same as Unmarshal
func UnmarshalPreprocessed(
raw map[string]any,
extraParsers ...UnmarshalFunc,
) (BaseApChain, []error) {
base := EmptyBaseObject{}
allParsers := slices.Concat(allInternalParsersExceptBase, extraParsers)
return chainParse(raw, &base, allParsers...)
}
// Find an attribute in an ActivityPub object of the given type
// Returns a pointer to the found attribute and whether it found it
// 2nd parameter is true if the attribute was found, false otherwise
func FindAttribute[T BaseApChain](object BaseApChain) (*T, bool) {
var obj T
var ok bool
// Try and cast object into wanted type
// If cast failed, do inner codeblock
// Try and cast again
for obj, ok = object.(T); !ok; obj, ok = object.(T) {
// Get the next attribute in the chain
object, ok = object.GetSelfOrBase()
// If this is the final object in the chain, cancel and return false
if !ok {
return nil, false
}
}
return &obj, true
}

View file

@ -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
}