Skip to content

Commit c148b83

Browse files
committed
reblogs and attachments now sort of work
1 parent 1d57c9d commit c148b83

20 files changed

+644
-813
lines changed

activitypub/activitypub.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func (e *Env) Log() *slog.Logger {
3333
func Followers(env *Env, w http.ResponseWriter, r *http.Request) error {
3434
var followers []*models.Relationship
3535
query := env.DB.Joins("JOIN actors ON actors.object_id = relationships.target_id and actors.name = ? and actors.domain = ?", chi.URLParam(r, "name"), r.Host)
36-
if err := query.Model(&models.Relationship{}).Preload("Actor").Find(&followers, "following = true").Error; err != nil {
36+
if err := query.Model(&models.Relationship{}).Scopes(models.PreloadActor).Find(&followers, "following = true").Error; err != nil {
3737
return err
3838
}
3939
return to.JSON(w, map[string]any{
@@ -53,7 +53,7 @@ func Followers(env *Env, w http.ResponseWriter, r *http.Request) error {
5353
func Following(env *Env, w http.ResponseWriter, r *http.Request) error {
5454
var following []*models.Relationship
5555
query := env.DB.Joins("JOIN actors ON actors.object_id = relationships.actor_id and actors.name = ? and actors.domain = ?", chi.URLParam(r, "name"), r.Host)
56-
if err := query.Model(&models.Relationship{}).Preload("Target").Find(&following, "following = true").Error; err != nil {
56+
if err := query.Model(&models.Relationship{}).Scopes(models.PreloadRelationshipTarget).Find(&following, "following = true").Error; err != nil {
5757
return err
5858
}
5959
return to.JSON(w, map[string]any{

activitypub/fetchers.go

+9-83
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package activitypub
33
import (
44
"context"
55
"fmt"
6-
"net/url"
76
"time"
87

98
"github.com/davecheney/pub/internal/algorithms"
@@ -12,79 +11,6 @@ import (
1211
"gorm.io/gorm"
1312
)
1413

15-
type RemoteActorFetcher struct {
16-
// signAs is the account that will be used to sign the request
17-
signAs *models.Account
18-
}
19-
20-
func NewRemoteActorFetcher(signAs *models.Account) *RemoteActorFetcher {
21-
return &RemoteActorFetcher{
22-
signAs: signAs,
23-
}
24-
}
25-
26-
func (f *RemoteActorFetcher) Fetch(ctx context.Context, uri string) (*models.Actor, error) {
27-
c, err := NewClient(f.signAs)
28-
if err != nil {
29-
return nil, err
30-
}
31-
var actor struct {
32-
Type string `json:"type"`
33-
// The Actor's unique global identifier.
34-
ID string `json:"id"`
35-
Inbox string `json:"inbox"`
36-
Outbox string `json:"outbox"`
37-
PreferredUsername string `json:"preferredUsername"`
38-
Name string `json:"name"`
39-
Summary string `json:"summary"`
40-
Icon struct {
41-
Type string `json:"type"`
42-
MediaType string `json:"mediaType"`
43-
URL string `json:"url"`
44-
} `json:"icon"`
45-
Image struct {
46-
Type string `json:"type"`
47-
MediaType string `json:"mediaType"`
48-
URL string `json:"url"`
49-
} `json:"image"`
50-
Endpoints struct {
51-
SharedInbox string `json:"sharedInbox"`
52-
} `json:"endpoints"`
53-
ManuallyApprovesFollowers bool `json:"manuallyApprovesFollowers"`
54-
Published time.Time `json:"published"`
55-
PublicKey struct {
56-
ID string `json:"id"`
57-
Owner string `json:"owner"`
58-
PublicKeyPem string `json:"publicKeyPem"`
59-
} `json:"publicKey"`
60-
Attachments []Attachment `json:"attachment"`
61-
}
62-
if err := c.Fetch(ctx, uri, &actor); err != nil {
63-
return nil, err
64-
}
65-
66-
published := actor.Published
67-
if published.IsZero() {
68-
published = time.Now()
69-
}
70-
71-
u, err := url.Parse(actor.ID)
72-
if err != nil {
73-
return nil, err
74-
}
75-
76-
return &models.Actor{
77-
ObjectID: snowflake.TimeToID(published),
78-
Type: models.ActorType(actor.Type),
79-
Name: actor.PreferredUsername,
80-
Domain: u.Host,
81-
// URI: actor.ID,
82-
// Avatar: actor.Icon.URL,
83-
// Header: actor.Image.URL,
84-
// Attributes: attachmentsToActorAttributes(actor.Attachments),
85-
}, nil
86-
}
87-
8814
func attachmentsToActorAttributes(attachments []Attachment) []*models.ActorAttribute {
8915
return algorithms.Map(
9016
algorithms.Filter(
@@ -236,12 +162,12 @@ func (f *RemoteStatusFetcher) Fetch(uri string) (*models.Status, error) {
236162
return st, nil
237163
}
238164

239-
func attachmentsToStatusAttachments(attachments []any) []*models.StatusAttachment {
240-
return algorithms.Map(
241-
algorithms.Map(
242-
attachments,
243-
mapFromAny,
244-
),
245-
objToStatusAttachment,
246-
)
247-
}
165+
// func attachmentsToStatusAttachments(attachments []any) []*models.StatusAttachment {
166+
// return algorithms.Map(
167+
// algorithms.Map(
168+
// attachments,
169+
// mapFromAny,
170+
// ),
171+
// objToStatusAttachment,
172+
// )
173+
// }

activitypub/inbox.go

+15-55
Original file line numberDiff line numberDiff line change
@@ -144,50 +144,18 @@ func (i *inboxProcessor) processUndoFollow(body map[string]any) error {
144144
}
145145

146146
func (i *inboxProcessor) processAnnounce(act *Activity) error {
147-
target := stringFromAny(act.Object)
148-
original, err := models.NewStatuses(i.db).FindOrCreateByURI(target)
149-
if err != nil {
150-
return err
151-
}
152-
actor, err := models.NewActors(i.db).FindOrCreateByURI(stringFromAny(act.Actor))
153-
if err != nil {
154-
return err
155-
}
156-
157147
var buf bytes.Buffer
158148
if err := json.MarshalFull(&buf, act); err != nil {
159149
return err
160150
}
161-
162151
var props map[string]any
163152
if err := json.UnmarshalFull(&buf, &props); err != nil {
164153
return err
165154
}
166155
obj := &models.Object{
167156
Properties: props,
168157
}
169-
if err := i.db.Create(&obj).Error; err != nil {
170-
return err
171-
}
172-
173-
updatedAt := act.Updated
174-
if updatedAt.IsZero() {
175-
updatedAt = obj.ID.ToTime()
176-
}
177-
178-
status := &models.Status{
179-
ObjectID: obj.ID,
180-
UpdatedAt: updatedAt,
181-
ActorID: actor.ObjectID,
182-
Actor: actor,
183-
Conversation: &models.Conversation{
184-
Visibility: "public",
185-
},
186-
Visibility: "public",
187-
ReblogID: &original.ObjectID,
188-
}
189-
190-
return i.db.Save(status).Error
158+
return i.db.Create(&obj).Error
191159
}
192160

193161
func (i *inboxProcessor) processAdd(act *Activity) error {
@@ -261,14 +229,6 @@ func (i *inboxProcessor) processCreate(create map[string]any) error {
261229
return err
262230
}
263231
return nil
264-
265-
// typ := stringFromAny(create["type"])
266-
// switch typ {
267-
// case "Note", "Question":
268-
// return i.processCreateNote(create)
269-
// default:
270-
// return fmt.Errorf("unknown create object type: %q", typ)
271-
// }
272232
}
273233

274234
func (i *inboxProcessor) processCreateNote(create map[string]any) error {
@@ -378,20 +338,20 @@ func inReplyToActorID(inReplyTo *models.Status) *snowflake.ID {
378338
return nil
379339
}
380340

381-
func objToStatusAttachment(obj map[string]any) *models.StatusAttachment {
382-
return &models.StatusAttachment{
383-
Attachment: models.Attachment{
384-
ID: snowflake.Now(),
385-
MediaType: stringFromAny(obj["mediaType"]),
386-
URL: stringFromAny(obj["url"]),
387-
Name: stringFromAny(obj["name"]),
388-
Width: intFromAny(obj["width"]),
389-
Height: intFromAny(obj["height"]),
390-
Blurhash: stringFromAny(obj["blurhash"]),
391-
FocalPoint: focalPoint(obj),
392-
},
393-
}
394-
}
341+
// func objToStatusAttachment(obj map[string]any) *models.StatusAttachment {
342+
// return &models.StatusAttachment{
343+
// Attachment: models.Attachment{
344+
// ID: snowflake.Now(),
345+
// MediaType: stringFromAny(obj["mediaType"]),
346+
// URL: stringFromAny(obj["url"]),
347+
// Name: stringFromAny(obj["name"]),
348+
// Width: intFromAny(obj["width"]),
349+
// Height: intFromAny(obj["height"]),
350+
// Blurhash: stringFromAny(obj["blurhash"]),
351+
// FocalPoint: focalPoint(obj),
352+
// },
353+
// }
354+
// }
395355

396356
func focalPoint(obj map[string]any) models.FocalPoint {
397357
focalPoint := anyToSlice(obj["focalPoint"])

activitypub/users.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func UsersShow(env *Env, w http.ResponseWriter, r *http.Request) error {
108108
"icon": map[string]any{
109109
"type": "Image",
110110
"mediaType": "image/jpeg",
111-
"url": actor.Avatar(),
111+
"url": "https://media.hachyderm.io/accounts/headers/109/364/117/028/564/263/original/c6b5edf498087717.jpeg",
112112
},
113113
})
114114
}

fetchactor.go

+5-33
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,9 @@ package main
33
import (
44
"context"
55
"fmt"
6-
"net/http"
7-
"os"
86

97
"github.com/davecheney/pub/activitypub"
10-
"github.com/davecheney/pub/mastodon"
118
"github.com/davecheney/pub/models"
12-
"github.com/go-json-experiment/json"
139
"gorm.io/gorm"
1410
)
1511

@@ -33,38 +29,14 @@ func (f *FetchActorCmd) Run(ctx *Context) error {
3329
return fmt.Errorf("failed to find account: %w", err)
3430
}
3531

36-
orig, err := models.NewActors(db).FindByURI(f.Actor)
32+
var props map[string]any
33+
client, err := activitypub.NewClient(account)
3734
if err != nil {
38-
return fmt.Errorf("failed to find actor: %w", err)
35+
return fmt.Errorf("failed to create client: %w", err)
3936
}
40-
41-
updated, err := activitypub.NewRemoteActorFetcher(account).Fetch(context.Background(), f.Actor)
42-
if err != nil {
37+
if err := client.Fetch(context.Background(), f.Actor, &props); err != nil {
4338
return fmt.Errorf("failed to fetch actor: %w", err)
4439
}
4540

46-
if !orig.ObjectID.ToTime().Equal(updated.ObjectID.ToTime()) {
47-
return fmt.Errorf("actor ID changed from %v to %v", orig.ObjectID, updated.ObjectID)
48-
}
49-
50-
// RemoteActorFetcher.Fetch will have created a new snowflake ID for the updated record
51-
// even if the created-at date has not changed because of the random component of the ID.
52-
// We need to update the ID to match the original record.
53-
updated.ObjectID = orig.ObjectID
54-
55-
req, _ := http.NewRequest("GET", updated.URI(), nil)
56-
ser := mastodon.NewSerialiser(req)
57-
json.MarshalFull(os.Stdout, map[string]any{
58-
"original": ser.Account(orig),
59-
"updated": ser.Account(updated),
60-
})
61-
62-
return db.Transaction(func(tx *gorm.DB) error {
63-
// delete actor attributes
64-
if err := tx.Where("actor_id = ?", orig.ObjectID).Delete(&models.ActorAttribute{}).Error; err != nil {
65-
return err
66-
}
67-
// save updated actor
68-
return tx.Session(&gorm.Session{FullSaveAssociations: true}).Updates(updated).Error
69-
})
41+
return db.Create(&models.Object{Properties: props}).Error
7042
}

main.go

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ var cli struct {
3333
Serve ServeCmd `cmd:"" help:"Serve a local web server."`
3434
ShowActor ShowActorCmd `cmd:"" help:"Display an actor."`
3535
SynchroniseFollowers SynchroniseFollowersCmd `cmd:"" help:"Synchronise followers."`
36+
RerunObjectHooks RerunObjectHooksCmd `cmd:"" help:"Rerun object hooks."`
3637
Follow FollowCmd `cmd:"" help:"Follow an object."`
3738
}
3839

mastodon/accounts.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func AccountsShow(env *Env, w http.ResponseWriter, r *http.Request) error {
1717
return err
1818
}
1919
var actor models.Actor
20-
if err := env.DB.Scopes(models.PreloadActor).Take(&actor, "id = ? ", chi.URLParam(r, "id")).Error; err != nil {
20+
if err := env.DB.Scopes(models.PreloadActor).Take(&actor, "object_id = ? ", chi.URLParam(r, "id")).Error; err != nil {
2121
return httpx.Error(http.StatusNotFound, err)
2222
}
2323
serialise := Serialiser{req: r}
@@ -47,8 +47,8 @@ func AccountsStatusesShow(env *Env, w http.ResponseWriter, r *http.Request) erro
4747
models.MaybeExcludeReblogs(r),
4848
models.MaybePinned(r),
4949
)
50-
query = query.Preload("Reaction", &models.Reaction{ActorID: user.Actor.ObjectID}) // reactions
51-
query = query.Preload("Reblog.Reaction", &models.Reaction{ActorID: user.Actor.ObjectID})
50+
query = query.Preload("Reaction", &models.Reaction{ActorID: user.Actor.ObjectID}).Preload("Reaction.Actor").Preload("Reaction.Actor.Object") // reactions
51+
query = query.Preload("Reblog.Reaction", &models.Reaction{ActorID: user.Actor.ObjectID}).Preload("Reblog.Reaction.Actor").Preload("Reblog.Reaction.Actor.Object")
5252
if err := query.Where("statuses.actor_id = ?", chi.URLParam(r, "id")).Find(&statuses).Error; err != nil {
5353
return err
5454
}
@@ -69,7 +69,7 @@ func AccountsFollowersShow(env *Env, w http.ResponseWriter, r *http.Request) err
6969
}
7070

7171
var followers []*models.Relationship
72-
if err := env.DB.Scopes(models.PaginateRelationship(r)).Preload("Target").Where("actor_id = ? and followed_by = true", chi.URLParam(r, "id")).Find(&followers).Error; err != nil {
72+
if err := env.DB.Scopes(models.PaginateRelationship(r), models.PreloadRelationshipTarget).Where("actor_id = ? and followed_by = true", chi.URLParam(r, "id")).Find(&followers).Error; err != nil {
7373
return err
7474
}
7575

@@ -90,7 +90,7 @@ func AccountsFollowingShow(env *Env, w http.ResponseWriter, r *http.Request) err
9090
return err
9191
}
9292
var following []*models.Relationship
93-
if err := env.DB.Scopes(models.PaginateRelationship(r)).Preload("Target").Preload("Target.Attributes").Where("actor_id = ? and following = true", chi.URLParam(r, "id")).Find(&following).Error; err != nil {
93+
if err := env.DB.Scopes(models.PaginateRelationship(r), models.PreloadRelationshipTarget).Where("actor_id = ? and following = true", chi.URLParam(r, "id")).Find(&following).Error; err != nil {
9494
return err
9595
}
9696

@@ -166,7 +166,7 @@ func AccountsFamiliarFollowersShow(env *Env, w http.ResponseWriter, req *http.Re
166166
}
167167
followers := env.DB.Select("target_id").Where("actor_id = ? and following = true", id).Table("relationships")
168168
var commonFollowers []*models.Relationship
169-
if err := env.DB.Preload("Target").Preload("Target.Attributes").Where("actor_id = ? and following = true and target_id in (?)", user.Actor.ObjectID, followers).Find(&commonFollowers).Error; err != nil {
169+
if err := env.DB.Scopes(models.PreloadRelationshipTarget).Where("actor_id = ? and following = true and target_id in (?)", user.Actor.ObjectID, followers).Find(&commonFollowers).Error; err != nil {
170170
return httpx.Error(http.StatusInternalServerError, err)
171171
}
172172
resp = append(resp, FamiliarFollowers{

mastodon/conversations.go

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package mastodon
22

33
import (
4-
"fmt"
54
"net/http"
65

76
"github.com/davecheney/pub/internal/algorithms"
@@ -16,21 +15,20 @@ func ConversationsIndex(env *Env, w http.ResponseWriter, r *http.Request) error
1615
}
1716

1817
var statuses []*models.Status
19-
scope := env.DB.Scopes(models.PaginateConversation(r)).Where("visibility = ?", "direct")
18+
query := env.DB.Scopes(models.PaginateConversation(r), models.PreloadStatus).Where("visibility = ?", "direct")
2019
switch r.URL.Query().Get("local") {
2120
case "":
22-
scope = scope.Joins("Actor")
21+
// nothing
2322
default:
24-
scope = scope.Joins("Actor").Where("Actor.domain = ?", r.Host)
23+
query = query.Where("Actor.domain = ?", r.Host)
2524
}
26-
27-
if err := scope.Order("statuses.id desc").Find(&statuses).Error; err != nil {
25+
if err := query.Order("statuses.object_id desc").Find(&statuses).Error; err != nil {
2826
return err
2927
}
30-
3128
if len(statuses) > 0 {
32-
w.Header().Set("Link", fmt.Sprintf("<https://%s/api/v1/timelines/public?max_id=%d>; rel=\"next\", <https://%s/api/v1/timelines/public?min_id=%d>; rel=\"prev\"", r.Host, statuses[len(statuses)-1].ObjectID, r.Host, statuses[0].ObjectID))
29+
linkHeader(w, r, statuses[0].ObjectID, statuses[len(statuses)-1].ObjectID)
3330
}
31+
3432
serialise := Serialiser{req: r}
3533
return to.JSON(w, algorithms.Map(statuses, serialise.Status))
3634
}

0 commit comments

Comments
 (0)