forgejo-federation/services/forgefed/actor.go

220 lines
4.9 KiB
Go

package forgefed
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"code.gitea.io/gitea/models/federation"
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
user_service "code.gitea.io/gitea/services/user"
ap "github.com/go-ap/activitypub"
)
func GetActor(id string) (*ap.Actor, error) {
client := http.Client{}
req, err := http.NewRequest("GET", id, nil)
if err != nil {
//Handle Error
}
req.Header = http.Header{
"Content-Type": {"application/activity+json"},
}
r, err := client.Do(req)
if err != nil {
return nil, err
}
defer r.Body.Close()
body, err := io.ReadAll(r.Body)
if err != nil {
return nil, err
}
actorObj := new(ap.Actor)
err = json.Unmarshal(body, &actorObj)
if err != nil {
return nil, err
}
return actorObj, nil
}
func GetPersonAvatar(ctx context.Context, person *ap.Person) ([]byte, error) {
avatarObj := new(ap.Image)
ap.CopyItemProperties(avatarObj, person.Icon)
log.Info("Getting avatar from link : %s", avatarObj.URL.GetLink().String())
r, err := http.Get(avatarObj.URL.GetLink().String())
if err != nil {
log.Error("Got error while fetching avatar fn: %w", err)
return nil, err
}
defer r.Body.Close()
return io.ReadAll(r.Body)
}
func SavePerson(ctx context.Context, person *ap.Person) (*user.User, error) {
fmt.Println(person.ID.String())
hostname, err := GetHostnameFromResource(person.ID.String())
exists, err := federation.FederatedHostExists(ctx, hostname)
if err != nil {
return nil, err
}
var federatedHost federation.FederatedHost
if exists {
x, err := federation.GetFederatdHost(ctx, hostname)
federatedHost = *x
if err != nil {
return nil, err
}
} else {
federatedHost := new(federation.FederatedHost)
federatedHost.HostFqdn = hostname
if err = federatedHost.Save(ctx); err != nil {
return nil, err
}
}
if err != nil {
return nil, err
}
u := new(user.User)
u.Name = "@" + person.PreferredUsername.String() + "@" + hostname
//panic(u.Name)
u.Email = person.PreferredUsername.String() + "@" + hostname
u.Website = person.URL.GetID().String()
u.KeepEmailPrivate = true
exist, err := user.GetUser(ctx, u)
if err != nil {
return nil, err
}
if exist {
return u, nil // TODO: must also check for federatedUser existence
}
if err = federation.CreatUser(ctx, u); err != nil {
return nil, err
}
avatar, err := GetPersonAvatar(ctx, person)
if err != nil {
log.Error("Got error while fetching avatar: %w", err)
return nil, err
}
if u.IsUploadAvatarChanged(avatar) {
_ = user_service.UploadAvatar(ctx, u, avatar)
}
if err = federation.CreateFederatedUser(ctx, u, &federatedHost); err != nil {
return nil, err
}
return u, nil
}
func GetActorFromUser(ctx context.Context, u *user.User) (*ap.Actor, error) {
alias := u.Name
webfingerRes, err := WebFingerLookup(alias)
if err != nil {
return nil, err
}
actorID := webfingerRes.GetActorLink().Href
return GetActor(actorID)
}
// Clean up remote actors (persons) without any followers in local instance
func CleanUpRemotePersons(ctx context.Context, olderThan time.Duration) error {
page := 0
for {
users, err := federation.GetRemoteUsersWithNoLocalFollowers(ctx, olderThan, page)
if len(users) == 0 {
break
}
if err != nil {
log.Trace("Error: CleanUpRemotePersons: %v", err)
return err
}
for _, u := range users {
err = user_service.DeleteUser(ctx, &u, false)
if err != nil {
log.Trace("Error: CleanUpRemotePersons: %v", err)
return err
}
}
page += 1
}
return nil
}
func UpdatePersonActor(ctx context.Context) error {
// NOTE: change of any of these don't matter at this point since we are
// ignoring actor's PreferredUsername and using their address to generate
// username and email. Ask suggestions from other devs.
//
// fmt.Println(person.ID.String())
// hostname, err := GetHostnameFromResource(person.ID.String())
//
//
// u := new(user.User)
// u.Name = "@" + person.PreferredUsername.String() + "@" + hostname
// //panic(u.Name)
// u.Email = person.PreferredUsername.String() + "@" + hostname
// u.Website = person.URL.GetID().String()
// u.KeepEmailPrivate = true
page := 0
for {
federatedUsers, err := federation.GetRemotePersons(ctx, page)
if len(federatedUsers) == 0 {
break
}
if err != nil {
log.Trace("Error: UpdatePersonActor: %v", err)
return err
}
for _, f := range federatedUsers {
log.Info("Updating users, got %s", f.ExternalID)
u, err := user.GetUserByName(ctx, f.ExternalID)
if err != nil {
log.Error("Got error while getting user: %w", err)
return err
}
person, err := GetActorFromUser(ctx, u)
if err != nil {
log.Error("Got error while fetching actor: %w", err)
return err
}
avatar, err := GetPersonAvatar(ctx, person)
if err != nil {
log.Error("Got error while fetching avatar: %w", err)
return err
}
if u.IsUploadAvatarChanged(avatar) {
_ = user_service.UploadAvatar(ctx, u, avatar)
}
}
page += 1
}
return nil
}