1 1d5ddf5d 2024-02-20 o // apub is an implementation of the ActivityPub protocol.
3 1d5ddf5d 2024-02-20 o // https://www.w3.org/TR/activitypub/
4 1d5ddf5d 2024-02-20 o // https://www.w3.org/TR/activitystreams-core/
5 1d5ddf5d 2024-02-20 o // https://www.w3.org/TR/activitystreams-vocabulary/
10 1d5ddf5d 2024-02-20 o "encoding/json"
20 1d5ddf5d 2024-02-20 o const AtContext string = "https://www.w3.org/ns/activitystreams"
22 25fed994 2024-03-12 o // ContentType is the MIME media type for ActivityPub.
23 1d5ddf5d 2024-02-20 o const ContentType string = "application/activity+json"
25 25fed994 2024-03-12 o // PublicCollection is the ActivityPub ID for the special collection indicating public access.
26 25fed994 2024-03-12 o // Any Activity addressed to this collection is meant to be available to all users,
27 25fed994 2024-03-12 o // authenticated or not.
28 77918b00 2024-03-07 o // See W3C Recommendation ActivityPub Section 5.6.
29 77918b00 2024-03-07 o const PublicCollection string = "https://www.w3.org/ns/activitystreams#Public"
31 71191436 2024-02-28 o var ErrNotExist = errors.New("no such activity")
33 1d5ddf5d 2024-02-20 o type Activity struct {
34 1d5ddf5d 2024-02-20 o AtContext string `json:"@context"`
35 1d5ddf5d 2024-02-20 o ID string `json:"id"`
36 1d5ddf5d 2024-02-20 o Type string `json:"type"`
37 1d5ddf5d 2024-02-20 o Name string `json:"name,omitempty"`
38 1d5ddf5d 2024-02-20 o Actor string `json:"actor,omitempty"`
39 1d5ddf5d 2024-02-20 o Username string `json:"preferredUsername,omitempty"`
40 77918b00 2024-03-07 o Summary string `json:"summary,omitempty"`
41 1d5ddf5d 2024-02-20 o Inbox string `json:"inbox,omitempty"`
42 1d5ddf5d 2024-02-20 o Outbox string `json:"outbox,omitempty"`
43 1d5ddf5d 2024-02-20 o To []string `json:"to,omitempty"`
44 1d5ddf5d 2024-02-20 o CC []string `json:"cc,omitempty"`
45 77918b00 2024-03-07 o Followers string `json:"followers,omitempty"`
46 1d5ddf5d 2024-02-20 o InReplyTo string `json:"inReplyTo,omitempty"`
47 1d5ddf5d 2024-02-20 o Published *time.Time `json:"published,omitempty"`
48 1d5ddf5d 2024-02-20 o AttributedTo string `json:"attributedTo,omitempty"`
49 1d5ddf5d 2024-02-20 o Content string `json:"content,omitempty"`
50 1d5ddf5d 2024-02-20 o MediaType string `json:"mediaType,omitempty"`
51 1d5ddf5d 2024-02-20 o Source struct {
52 1d5ddf5d 2024-02-20 o Content string `json:"content,omitempty"`
53 1d5ddf5d 2024-02-20 o MediaType string `json:"mediaType,omitempty"`
54 1d5ddf5d 2024-02-20 o } `json:"source,omitempty"`
55 77918b00 2024-03-07 o PublicKey *PublicKey `json:"publicKey,omitempty"`
56 77918b00 2024-03-07 o Audience string `json:"audience,omitempty"`
57 77918b00 2024-03-07 o Object json.RawMessage `json:"object,omitempty"`
60 1d5ddf5d 2024-02-20 o func (act *Activity) UnmarshalJSON(b []byte) error {
61 1d5ddf5d 2024-02-20 o type Alias Activity
62 1d5ddf5d 2024-02-20 o aux := &struct {
63 1d5ddf5d 2024-02-20 o AtContext interface{} `json:"@context"`
64 1d5ddf5d 2024-02-20 o Object interface{}
67 1d5ddf5d 2024-02-20 o Alias: (*Alias)(act),
69 1d5ddf5d 2024-02-20 o if err := json.Unmarshal(b, &aux); err != nil {
72 1d5ddf5d 2024-02-20 o switch v := aux.AtContext.(type) {
74 1d5ddf5d 2024-02-20 o act.AtContext = v
75 1d5ddf5d 2024-02-20 o case []interface{}:
76 1d5ddf5d 2024-02-20 o if vv, ok := v[0].(string); ok {
77 1d5ddf5d 2024-02-20 o act.AtContext = vv
83 25fed994 2024-03-12 o // Unwrap returns the JSON-encoded Activity, if any, enclosed in act.
84 25fed994 2024-03-12 o // The Activity may be referenced by ID,
85 25fed994 2024-03-12 o // in which case the activity is looked up by client or by
86 25fed994 2024-03-12 o // apub.defaultClient if client is nil.
87 1d5ddf5d 2024-02-20 o func (act *Activity) Unwrap(client *Client) (*Activity, error) {
88 1d5ddf5d 2024-02-20 o if act.Object == nil {
89 1d5ddf5d 2024-02-20 o return nil, errors.New("no wrapped activity")
92 1d5ddf5d 2024-02-20 o var buf io.Reader
93 1d5ddf5d 2024-02-20 o buf = bytes.NewReader(act.Object)
94 1d5ddf5d 2024-02-20 o if strings.HasPrefix(string(act.Object), "https") {
95 1d5ddf5d 2024-02-20 o if client == nil {
96 1d5ddf5d 2024-02-20 o return Lookup(string(act.Object))
98 1d5ddf5d 2024-02-20 o return client.Lookup(string(act.Object))
100 1d5ddf5d 2024-02-20 o return Decode(buf)
103 1d5ddf5d 2024-02-20 o func Decode(r io.Reader) (*Activity, error) {
104 1d5ddf5d 2024-02-20 o var a Activity
105 1d5ddf5d 2024-02-20 o if err := json.NewDecoder(r).Decode(&a); err != nil {
106 1d5ddf5d 2024-02-20 o return nil, fmt.Errorf("decode activity: %w", err)
108 1d5ddf5d 2024-02-20 o return &a, nil
111 71191436 2024-02-28 o func DecodeActor(r io.Reader) (*Actor, error) {
112 71191436 2024-02-28 o a, err := Decode(r)
113 71191436 2024-02-28 o if err != nil {
114 71191436 2024-02-28 o return nil, err
116 71191436 2024-02-28 o return activityToActor(a), nil
119 1d5ddf5d 2024-02-20 o type Actor struct {
120 71191436 2024-02-28 o AtContext string `json:"@context"`
121 71191436 2024-02-28 o ID string `json:"id"`
122 71191436 2024-02-28 o Type string `json:"type"`
123 71191436 2024-02-28 o Name string `json:"name"`
124 71191436 2024-02-28 o Username string `json:"preferredUsername"`
125 77918b00 2024-03-07 o Summary string `json:"summary,omitempty"`
126 71191436 2024-02-28 o Inbox string `json:"inbox"`
127 71191436 2024-02-28 o Outbox string `json:"outbox"`
128 77918b00 2024-03-07 o Followers string `json:"followers"`
129 71191436 2024-02-28 o Published *time.Time `json:"published,omitempty"`
130 71191436 2024-02-28 o PublicKey PublicKey `json:"publicKey"`
133 1d5ddf5d 2024-02-20 o type PublicKey struct {
134 1d5ddf5d 2024-02-20 o ID string `json:"id"`
135 1d5ddf5d 2024-02-20 o Owner string `json:"owner"`
136 1d5ddf5d 2024-02-20 o PublicKeyPEM string `json:"publicKeyPem"`
139 25fed994 2024-03-12 o // Address generates the most likely address of the Actor.
140 25fed994 2024-03-12 o // The Actor's name (not the username) is used as the address' proper name, if present.
141 25fed994 2024-03-12 o // Implementors should verify the address using WebFinger.
142 25fed994 2024-03-12 o // For example, the followers address for Actor ID
143 25fed994 2024-03-12 o // https://hachyderm.io/users/otl is:
145 25fed994 2024-03-12 o // "Oliver Lowe" <otl+followers@hachyderm.io>
146 1d5ddf5d 2024-02-20 o func (a *Actor) Address() *mail.Address {
147 71191436 2024-02-28 o if a.Username == "" && a.Name == "" {
148 71191436 2024-02-28 o return &mail.Address{"", a.ID}
150 1d5ddf5d 2024-02-20 o trimmed := strings.TrimPrefix(a.ID, "https://")
151 1d5ddf5d 2024-02-20 o host, _, _ := strings.Cut(trimmed, "/")
152 1d5ddf5d 2024-02-20 o addr := fmt.Sprintf("%s@%s", a.Username, host)
153 1d5ddf5d 2024-02-20 o return &mail.Address{a.Name, addr}
156 25fed994 2024-03-12 o // FollowersAddress generates a non-standard address representing the Actor's followers
157 25fed994 2024-03-12 o // using plus addressing.
158 25fed994 2024-03-12 o // It is the Actor's address username part with a "+followers" suffix.
159 25fed994 2024-03-12 o // The address cannot be resolved using WebFinger.
161 25fed994 2024-03-12 o // For example, the followers address for Actor ID
162 25fed994 2024-03-12 o // https://hachyderm.io/users/otl is:
164 25fed994 2024-03-12 o // "Oliver Lowe (followers)" <otl+followers@hachyderm.io>
165 77918b00 2024-03-07 o func (a *Actor) FollowersAddress() *mail.Address {
166 77918b00 2024-03-07 o if a.Followers == "" {
167 77918b00 2024-03-07 o return &mail.Address{"", ""}
169 77918b00 2024-03-07 o addr := a.Address()
170 77918b00 2024-03-07 o user, domain, found := strings.Cut(addr.Address, "@")
172 77918b00 2024-03-07 o return &mail.Address{"", ""}
174 77918b00 2024-03-07 o addr.Address = fmt.Sprintf("%s+followers@%s", user, domain)
175 77918b00 2024-03-07 o if addr.Name != "" {
176 77918b00 2024-03-07 o addr.Name += " (followers)"