Add debug handler for fetching a remote actor
All checks were successful
/ docker (push) Successful in 3m59s
All checks were successful
/ docker (push) Successful in 3m59s
Will be used later to add to internal db
This commit is contained in:
parent
d4f2f66807
commit
f8b3a6ff06
12 changed files with 313 additions and 156 deletions
101
activitypub/import.go
Normal file
101
activitypub/import.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.mstar.dev/mstar/goutils/sliceutils"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
apshared "git.mstar.dev/mstar/linstrom/activitypub/shared"
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
webshared "git.mstar.dev/mstar/linstrom/web/shared"
|
||||
)
|
||||
|
||||
func ImportRemoteAccount(targetName string) (string, error) {
|
||||
type InboundUserKey struct {
|
||||
Id string `json:"id"`
|
||||
Owner string `json:"owner"`
|
||||
Pem string `json:"publicKeyPem"`
|
||||
}
|
||||
type InboundUserMedia struct {
|
||||
Type string `json:"type"`
|
||||
Url string `json:"url"`
|
||||
MediaType string `json:"mediaType"`
|
||||
}
|
||||
type InboundUser struct {
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
PreferredUsername string `json:"preferredUsername"`
|
||||
Inbox string `json:"inbox"`
|
||||
PublicKey *InboundUserKey `json:"publicKey"`
|
||||
Published *time.Time `json:"published"`
|
||||
DisplayName *string `json:"name"`
|
||||
Description *string `json:"summary,omitempty"`
|
||||
PublicUrl *string `json:"url"`
|
||||
Icon *InboundUserMedia `json:"icon,omitempty"`
|
||||
Banner *InboundUserMedia `json:"image,omitempty"`
|
||||
Discoverable *bool `json:"discoverable"`
|
||||
Location *string `json:"vcard:Address,omitempty"`
|
||||
Birthday *string `json:"vcard:bday,omitempty"`
|
||||
SpeakAsCat bool `json:"speakAsCat"`
|
||||
IsCat bool `json:"isCat"`
|
||||
RestrictedFollow *bool `json:"manuallyApprovesFollowers"`
|
||||
}
|
||||
// Get the target user's link first
|
||||
webfinger, err := apshared.GetAccountWebfinger(targetName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
selfLinks := sliceutils.Filter(webfinger.Links, func(t apshared.LinkData) bool {
|
||||
return t.Relation == "self"
|
||||
})
|
||||
if len(selfLinks) == 0 {
|
||||
return "", errors.New("No self link")
|
||||
}
|
||||
APLink := selfLinks[0]
|
||||
req, err := http.NewRequest("GET", *APLink.Href, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Add("Accept", "application/activity+json")
|
||||
|
||||
// Server actor key for signing
|
||||
linstromActor, err := dbgen.User.Where(dbgen.User.Username.Eq("linstrom")).First()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var keyBytes []byte
|
||||
if config.GlobalConfig.Experimental.UseEd25519Keys {
|
||||
keyBytes = linstromActor.PrivateKeyEd
|
||||
} else {
|
||||
keyBytes = linstromActor.PrivateKeyRsa
|
||||
}
|
||||
|
||||
// Sign and send
|
||||
err = webshared.SignRequest(req, linstromActor.ID+"#main-key", keyBytes, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
response, err := webshared.RequestClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != 200 {
|
||||
return "", errors.New("Bad status")
|
||||
}
|
||||
var data InboundUser
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
log.Info().Bytes("body", body).Msg("Body from request")
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.Info().Any("received-data", data).Msg("Response data")
|
||||
return "", nil
|
||||
}
|
47
activitypub/shared/types/context.go
Normal file
47
activitypub/shared/types/context.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package types
|
||||
|
||||
var BaseLdContext = []any{
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
map[string]any{
|
||||
"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",
|
||||
"indexable": "toot:indexable",
|
||||
"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",
|
||||
"_misskey_followedMessage": "misskey:_misskey_followedMessage",
|
||||
"_misskey_requireSigninToViewContents": "misskey:_misskey_requireSigninToViewContents",
|
||||
"_misskey_makeNotesFollowersOnlyBefore": "misskey:_misskey_makeNotesFollowersOnlyBefore",
|
||||
"_misskey_makeNotesHiddenBefore": "misskey:_misskey_makeNotesHiddenBefore",
|
||||
"_misskey_license": "misskey:_misskey_license",
|
||||
"freeText": map[string]string{
|
||||
"@id": "misskey:freeText",
|
||||
"@type": "schema:text",
|
||||
},
|
||||
"isCat": "misskey:isCat",
|
||||
"firefish": "https://joinfirefish.org/ns#",
|
||||
"speakAsCat": "firefish:speakAsCat",
|
||||
"sharkey": "https://joinsharkey.org/ns#",
|
||||
"hideOnlineStatus": "sharkey:hideOnlineStatus",
|
||||
"backgroundUrl": "sharkey:backgroundUrl",
|
||||
"listenbrainz": "sharkey:listenbrainz",
|
||||
"enableRss": "sharkey:enableRss",
|
||||
"vcard": "http://www.w3.org/2006/vcard/ns#",
|
||||
},
|
||||
}
|
19
activitypub/shared/util.go
Normal file
19
activitypub/shared/util.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package apshared
|
||||
|
||||
import "strings"
|
||||
|
||||
type InvalidFullHandleError struct {
|
||||
Raw string
|
||||
}
|
||||
|
||||
func (i InvalidFullHandleError) Error() string {
|
||||
return "Invalid full handle"
|
||||
}
|
||||
|
||||
func SplitFullHandle(full string) (string, string, error) {
|
||||
splits := strings.Split(strings.TrimPrefix(full, "@"), "@")
|
||||
if len(splits) != 2 {
|
||||
return "", "", InvalidFullHandleError{full}
|
||||
}
|
||||
return splits[0], splits[1], nil
|
||||
}
|
90
activitypub/shared/webfinger.go
Normal file
90
activitypub/shared/webfinger.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package apshared
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
webshared "git.mstar.dev/mstar/linstrom/web/shared"
|
||||
)
|
||||
|
||||
var ErrNoApUrl = errors.New("no Activitypub url in webfinger")
|
||||
|
||||
type LinkData struct {
|
||||
// What type of link this is
|
||||
// - `self` refers to the activitypub object and has Type and Href set
|
||||
// - `http://webfinger.net/rel/profile-page` refers to the public webpage of the account and has Type and Href set
|
||||
// - `http://ostatus.org/schema/1.0/subscribe` provides a template for subscribing/following the account. Has Template set
|
||||
// Template will contain a `{uri}` part with which to replace idk yet
|
||||
Relation string `json:"rel"`
|
||||
// The content type of the url
|
||||
Type *string `json:"type"`
|
||||
// The url
|
||||
Href *string `json:"href"`
|
||||
// Template to use for something
|
||||
Template *string `json:"template"`
|
||||
}
|
||||
|
||||
// Data returned from a webfinger response (and also sent when asked for an account via webfinger)
|
||||
type WebfingerData struct {
|
||||
// What this webfinger data refers to. Accounts are usually `acct:username@host`
|
||||
Subject string `json:"subject"`
|
||||
// List of links related to the account
|
||||
Links []LinkData `json:"links"`
|
||||
}
|
||||
|
||||
var ErrAccountNotFound = errors.New("account not found")
|
||||
var ErrRemoteServerFailed = errors.New("remote server errored out")
|
||||
|
||||
// Get the webfinger data for a given full account handle (like @bob@example.com or bob@example.com)
|
||||
func GetAccountWebfinger(fullHandle string) (*WebfingerData, error) {
|
||||
// First get the handle and server domain from the full handle (and ensure it's a correctly formatted one)
|
||||
handle, server, err := SplitFullHandle(fullHandle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webfingerRequest, err := http.NewRequest(
|
||||
// Webfinger requests are GET
|
||||
"GET",
|
||||
// The webfinger url is located at <domain>/.well-known/webfinger
|
||||
// The url parameter `resource` tells the remote server what data we want, should always be an account
|
||||
// The prefix `acct:<full-handle>` (where full-handle is in the form of bob@example.com)
|
||||
// tells the remote server that we want the account data of the given account handle
|
||||
"https://"+server+"/.well-known/webfinger?resource=acct:"+handle+"@"+server,
|
||||
// No request body since it's a get request and we only need the url parameter
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Then send the request
|
||||
result, err := webshared.RequestClient.Do(webfingerRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Code 404 indicates that the account doesn't exist
|
||||
if result.StatusCode == 404 {
|
||||
return nil, ErrAccountNotFound
|
||||
} else if result.StatusCode != 200 {
|
||||
// And anything other than 200 or 404 is an error for now
|
||||
// TODO: Add information gathering here to figure out what the remote server's problem is
|
||||
return nil, ErrRemoteServerFailed
|
||||
}
|
||||
|
||||
// Get the body data from the response
|
||||
data, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Then try and parse it into webfinger data
|
||||
webfinger := WebfingerData{}
|
||||
err = json.Unmarshal(data, &webfinger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &webfinger, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue