Blob


1 // apub is an implementation of the ActivityPub protocol.
2 //
3 // https://www.w3.org/TR/activitypub/
4 // https://www.w3.org/TR/activitystreams-core/
5 // https://www.w3.org/TR/activitystreams-vocabulary/
6 package apub
8 import (
9 "bytes"
10 "encoding/json"
11 "errors"
12 "fmt"
13 "io"
14 "net/mail"
15 "strings"
16 "time"
17 )
19 // @context
20 const AtContext string = "https://www.w3.org/ns/activitystreams"
22 const ContentType string = "application/activity+json"
24 const AcceptMediaType string = `application/activity+json; profile="https://www.w3.org/ns/activitystreams"`
26 const ToEveryone string = "https://www.w3.org/ns/activitystreams#Public"
28 var ErrNotExist = errors.New("no such activity")
30 type Activity struct {
31 AtContext string `json:"@context"`
32 ID string `json:"id"`
33 Type string `json:"type"`
34 Name string `json:"name,omitempty"`
35 Actor string `json:"actor,omitempty"`
36 Username string `json:"preferredUsername,omitempty"`
37 Summary string `json:"summary"`
38 Inbox string `json:"inbox,omitempty"`
39 Outbox string `json:"outbox,omitempty"`
40 To []string `json:"to,omitempty"`
41 CC []string `json:"cc,omitempty"`
42 InReplyTo string `json:"inReplyTo,omitempty"`
43 Published *time.Time `json:"published,omitempty"`
44 AttributedTo string `json:"attributedTo,omitempty"`
45 Content string `json:"content,omitempty"`
46 MediaType string `json:"mediaType,omitempty"`
47 Source struct {
48 Content string `json:"content,omitempty"`
49 MediaType string `json:"mediaType,omitempty"`
50 } `json:"source,omitempty"`
51 Audience string `json:"audience,omitempty"`
52 Object json.RawMessage `json:"object,omitempty"`
53 }
55 func (act *Activity) UnmarshalJSON(b []byte) error {
56 type Alias Activity
57 aux := &struct {
58 AtContext interface{} `json:"@context"`
59 Object interface{}
60 *Alias
61 }{
62 Alias: (*Alias)(act),
63 }
64 if err := json.Unmarshal(b, &aux); err != nil {
65 return err
66 }
67 switch v := aux.AtContext.(type) {
68 case string:
69 act.AtContext = v
70 case []interface{}:
71 if vv, ok := v[0].(string); ok {
72 act.AtContext = vv
73 }
74 }
75 return nil
76 }
78 func (act *Activity) Unwrap(client *Client) (*Activity, error) {
79 if act.Object == nil {
80 return nil, errors.New("no wrapped activity")
81 }
83 var buf io.Reader
84 buf = bytes.NewReader(act.Object)
85 if strings.HasPrefix(string(act.Object), "https") {
86 if client == nil {
87 return Lookup(string(act.Object))
88 }
89 return client.Lookup(string(act.Object))
90 }
91 return Decode(buf)
92 }
94 func Decode(r io.Reader) (*Activity, error) {
95 var a Activity
96 if err := json.NewDecoder(r).Decode(&a); err != nil {
97 return nil, fmt.Errorf("decode activity: %w", err)
98 }
99 return &a, nil
102 func DecodeActor(r io.Reader) (*Actor, error) {
103 a, err := Decode(r)
104 if err != nil {
105 return nil, err
107 return activityToActor(a), nil
110 type Actor struct {
111 AtContext string `json:"@context"`
112 ID string `json:"id"`
113 Type string `json:"type"`
114 Name string `json:"name"`
115 Username string `json:"preferredUsername"`
116 Summary string `json:"summary"`
117 Inbox string `json:"inbox"`
118 Outbox string `json:"outbox"`
119 Published *time.Time `json:"published,omitempty"`
120 PublicKey PublicKey `json:"publicKey"`
123 type PublicKey struct {
124 ID string `json:"id"`
125 Owner string `json:"owner"`
126 PublicKeyPEM string `json:"publicKeyPem"`
129 func (a *Actor) Address() *mail.Address {
130 if a.Username == "" && a.Name == "" {
131 return &mail.Address{"", a.ID}
133 trimmed := strings.TrimPrefix(a.ID, "https://")
134 host, _, _ := strings.Cut(trimmed, "/")
135 addr := fmt.Sprintf("%s@%s", a.Username, host)
136 return &mail.Address{a.Name, addr}