207 lines
6.3 KiB
Go
207 lines
6.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
func ImportProfile(fromSession *AtprotoSession, toSession *AtprotoSession, dryrun bool, verbose bool) error {
|
|
fmt.Printf("fetching profile record from @%s...\n", fromSession.Handle)
|
|
|
|
reqUrl, _ := url.Parse(fromSession.PdsUrl + "/xrpc/com.atproto.repo.getRecord")
|
|
reqUrl.RawQuery = url.Values{
|
|
"repo": { fromSession.Did },
|
|
"collection": { "app.bsky.actor.profile" },
|
|
"rkey": { "self" },
|
|
}.Encode()
|
|
req, _ := http.NewRequest(http.MethodGet, reqUrl.String(), nil)
|
|
|
|
res, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("failed to fetch profile: %v\n", err))
|
|
}
|
|
|
|
getProfileResponse := AtprotoGetRecordResponse{}
|
|
err = json.NewDecoder(res.Body).Decode(&getProfileResponse)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("failed to parse PDS response: %v\n", err))
|
|
}
|
|
profile := BskyActorProfile{}
|
|
err = json.Unmarshal(getProfileResponse.Value, &profile)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("failed to parse source profile data: %v\n", err))
|
|
}
|
|
|
|
newProfile := BskyActorProfile{
|
|
LexiconTypeID: "app.bsky.actor.profile",
|
|
DisplayName: profile.DisplayName,
|
|
Description: profile.Description,
|
|
Labels: profile.Labels,
|
|
}
|
|
|
|
if verbose {
|
|
fmt.Printf(
|
|
"\nimporting profile details:\n" +
|
|
"- display name: %s\n" +
|
|
"- description:\n%s\n\n",
|
|
*newProfile.DisplayName,
|
|
*newProfile.Description,
|
|
)
|
|
}
|
|
|
|
if !dryrun {
|
|
if profile.Avatar != nil {
|
|
// import avatar
|
|
avatarBytes, err := GetAtprotoBlob(fromSession.PdsUrl, fromSession.Did, profile.Avatar.Ref.Link)
|
|
if err != nil { return errors.New(fmt.Sprintf("failed to download avatar: %v\n", err)) }
|
|
|
|
avatarBlob, err := UploadAtprotoBlob(toSession, avatarBytes, profile.Avatar.MimeType)
|
|
if err != nil { return errors.New(fmt.Sprintf("failed to upload avatar: %v\n", err)) }
|
|
newProfile.Avatar = avatarBlob
|
|
}
|
|
|
|
if profile.Banner != nil {
|
|
// import banner
|
|
bannerBytes, err := GetAtprotoBlob(fromSession.PdsUrl, fromSession.Did, profile.Banner.Ref.Link)
|
|
if err != nil { return errors.New(fmt.Sprintf("failed to download banner: %v\n", err)) }
|
|
|
|
bannerBlob, err := UploadAtprotoBlob(toSession, bannerBytes, profile.Banner.MimeType)
|
|
if err != nil { return errors.New(fmt.Sprintf("failed to upload banner: %v\n", err)) }
|
|
newProfile.Banner = bannerBlob
|
|
}
|
|
|
|
// import all details
|
|
_, err = PutAtprotoRecord(toSession, "app.bsky.actor.profile", newProfile)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("failed to update profile record: %v\n", err))
|
|
}
|
|
}
|
|
fmt.Printf("profile imported from @%s to @%s successfully.\n", fromSession.Handle, toSession.Handle)
|
|
|
|
return nil
|
|
}
|
|
|
|
func ImportFollows(fromSession *AtprotoSession, toSession *AtprotoSession, dryrun bool, verbose bool) error {
|
|
followDids := []string{}
|
|
cursor := ""
|
|
|
|
fmt.Printf("fetching follow records from @%s...\n", fromSession.Handle)
|
|
for {
|
|
reqUrl, _ := url.Parse(fromSession.PdsUrl + "/xrpc/app.bsky.graph.getFollows")
|
|
reqUrlValues := url.Values{
|
|
"actor": { fromSession.Did },
|
|
"limit": { "100" },
|
|
}
|
|
if cursor != "" { reqUrlValues.Set("cursor", cursor) }
|
|
reqUrl.RawQuery = reqUrlValues.Encode()
|
|
req, _ := http.NewRequest(http.MethodGet, reqUrl.String(), nil)
|
|
req.Header.Set("Authorization", "Bearer " + fromSession.AccessJwt)
|
|
|
|
res, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("failed to fetch follows: %v\n", err))
|
|
}
|
|
|
|
getFollowsResponse := BskyGetFollowsResponse{}
|
|
err = json.NewDecoder(res.Body).Decode(&getFollowsResponse)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("failed to parse PDS response: %v\n", err))
|
|
}
|
|
|
|
for _, profile := range getFollowsResponse.Follows {
|
|
if verbose {
|
|
fmt.Printf("following: %s - %s\n", profile.Handle, *profile.DisplayName)
|
|
}
|
|
followDids = append(followDids, profile.Did)
|
|
}
|
|
if getFollowsResponse.Cursor == nil {
|
|
break
|
|
}
|
|
cursor = *getFollowsResponse.Cursor
|
|
}
|
|
fmt.Printf("fetched %d follow records.\n", len(followDids))
|
|
|
|
fmt.Printf("importing follow records to @%s...\n", toSession.Handle)
|
|
successful := 0
|
|
for i, did := range followDids {
|
|
if dryrun {
|
|
if verbose {
|
|
fmt.Printf("(%d/%d) followed %s (dry run)\n", i, len(followDids), did)
|
|
}
|
|
successful += 1
|
|
continue
|
|
}
|
|
record := BskyGraphFollow{
|
|
Subject: did,
|
|
CreatedAt: time.Now().Format(time.RFC3339),
|
|
}
|
|
recordJson, _ := json.Marshal(record)
|
|
newRecord, err := CreateAtprotoRecord(toSession, "app.bsky.graph.follow", recordJson)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "warn: failed to create follow record for %s: %v\n", did, err)
|
|
continue
|
|
}
|
|
if verbose {
|
|
fmt.Printf("(%d/%d) followed %s (%s)\n", i, len(followDids), did, newRecord.Uri)
|
|
}
|
|
successful += 1
|
|
}
|
|
|
|
fmt.Printf(
|
|
"%d/%d follow records imported from @%s to @%s successfully!\n",
|
|
successful,
|
|
len(followDids),
|
|
fromSession.Handle,
|
|
toSession.Handle,
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
func ImportPreferences(fromSession *AtprotoSession, toSession *AtprotoSession, dryrun bool, verbose bool) error {
|
|
fmt.Printf("fetching preferences from @%s...\n", fromSession.Handle)
|
|
|
|
req, _ := http.NewRequest(http.MethodGet, fromSession.PdsUrl + "/xrpc/app.bsky.actor.getPreferences", nil)
|
|
req.Header.Set("Authorization", "Bearer " + fromSession.AccessJwt)
|
|
res, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("failed to fetch preferences: %v\n", err))
|
|
}
|
|
|
|
// // TODO: BskyGetPreferencesResponse
|
|
data := BskyActorPreferences{}
|
|
err = json.NewDecoder(res.Body).Decode(&data)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("failed to parse PDS response: %v\n", err))
|
|
}
|
|
fmt.Printf("fetched %d preferences.\n", len(data.Preferences))
|
|
|
|
fmt.Printf("importing preferences to @%s...\n", toSession.Handle)
|
|
if !dryrun {
|
|
reqBodyBytes, _ := json.Marshal(data)
|
|
req, _ := http.NewRequest(http.MethodPost, toSession.PdsUrl + "/xrpc/app.bsky.actor.putPreferences", bytes.NewReader(reqBodyBytes))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer " + toSession.AccessJwt)
|
|
|
|
res, err := http.DefaultClient.Do(req)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if res.StatusCode != http.StatusOK {
|
|
errBody := ErrorResponse{}
|
|
json.NewDecoder(res.Body).Decode(&errBody)
|
|
return errors.New(fmt.Sprintf("%s: %s", errBody.Error, errBody.Message))
|
|
}
|
|
}
|
|
|
|
fmt.Printf("preferences imported successfully!\n")
|
|
|
|
return nil
|
|
}
|