tidy-up refactor, add readme
This commit is contained in:
parent
6bde84b7e3
commit
cd48757272
6 changed files with 104 additions and 77 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.obsidian
|
39
README.md
Normal file
39
README.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# migratesky 🦋
|
||||||
|
handy tools for migrating your bluesky account to a new DID!
|
||||||
|
|
||||||
|
## build
|
||||||
|
```sh
|
||||||
|
go build -o migratesky .
|
||||||
|
```
|
||||||
|
|
||||||
|
## usage
|
||||||
|
```sh
|
||||||
|
migratesky <-from <identifier>> <-frompass <password>> <-to <identifier>> <-topass <identifier>> <-pds <pds-url>> [collections...]`
|
||||||
|
```
|
||||||
|
|
||||||
|
### example
|
||||||
|
```sh
|
||||||
|
# migrate the profile, follows, and preferences from @old.example.org to @new.example.org:
|
||||||
|
migratesky -from old.example.org -frompass $SRC_PASSWORD \
|
||||||
|
-to new.example.org -topass $DEST_PASSWORD \
|
||||||
|
-pds https://my.awesome.pds.example.org \
|
||||||
|
profile follows preferences
|
||||||
|
```
|
||||||
|
|
||||||
|
### required arguments:
|
||||||
|
- `-from <identifier>`: the source account handle or DID.
|
||||||
|
- `-frompass <password>`: the source account password.
|
||||||
|
- `-to <identifier>`: the destination account handle or DID.
|
||||||
|
- `-topass <password>`: the destination account password.
|
||||||
|
- `-pds <pds-url>`: the full https:// url of the destination account's PDS.
|
||||||
|
|
||||||
|
### collections:
|
||||||
|
- `profile`: imports account profile, including avatar, banner, display name, and description.
|
||||||
|
- `follows`: imports all following accounts.
|
||||||
|
- `preferences`: imports account preferences; including feeds, labeller settings, and content filters.
|
||||||
|
|
||||||
|
### optional arguments:
|
||||||
|
- `-frompds <pds-url>`: the source account's PDS url (defaults to the value of -pds).
|
||||||
|
- `-dryrun`: does not import follow records; good for sanity testing!
|
||||||
|
- `-v`: verbose output
|
||||||
|
- `-help`: shows this help message.
|
18
atproto.go
18
atproto.go
|
@ -20,6 +20,7 @@ type (
|
||||||
RefreshJwt string `json:"refreshJwt"`
|
RefreshJwt string `json:"refreshJwt"`
|
||||||
Handle string `json:"handle"`
|
Handle string `json:"handle"`
|
||||||
Did string `json:"did"`
|
Did string `json:"did"`
|
||||||
|
PdsUrl string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
AtprotoIdentity struct {
|
AtprotoIdentity struct {
|
||||||
|
@ -120,12 +121,13 @@ func CreateSession (pdsUrl string, identifier string, password string) (*Atproto
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf("failed to parse PDS response: %v\n", err))
|
return nil, errors.New(fmt.Sprintf("failed to parse PDS response: %v\n", err))
|
||||||
}
|
}
|
||||||
|
session.PdsUrl = pdsUrl
|
||||||
|
|
||||||
return &session, nil
|
return &session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResolveAtprotoHandle(session *AtprotoSession, pdsUrl string, handle string) (string, error) {
|
func ResolveAtprotoHandle(session *AtprotoSession, handle string) (string, error) {
|
||||||
reqUrl, _ := url.Parse(pdsUrl + "/xrpc/com.atproto.identity.resolveHandle")
|
reqUrl, _ := url.Parse(session.PdsUrl + "/xrpc/com.atproto.identity.resolveHandle")
|
||||||
reqUrl.RawQuery = url.Values{
|
reqUrl.RawQuery = url.Values{
|
||||||
"handle": { handle },
|
"handle": { handle },
|
||||||
}.Encode()
|
}.Encode()
|
||||||
|
@ -151,7 +153,7 @@ func ResolveAtprotoHandle(session *AtprotoSession, pdsUrl string, handle string)
|
||||||
return identity.Did, nil
|
return identity.Did, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateAtprotoRecord(session *AtprotoSession, pdsUrl string, collection string, record json.RawMessage) (*AtprotoUriCid, error) {
|
func CreateAtprotoRecord(session *AtprotoSession, collection string, record json.RawMessage) (*AtprotoUriCid, error) {
|
||||||
reqBody, reqBodyBytes := AtprotoCreateRecord{
|
reqBody, reqBodyBytes := AtprotoCreateRecord{
|
||||||
Repo: session.Did,
|
Repo: session.Did,
|
||||||
Collection: collection,
|
Collection: collection,
|
||||||
|
@ -159,7 +161,7 @@ func CreateAtprotoRecord(session *AtprotoSession, pdsUrl string, collection stri
|
||||||
}, new(bytes.Buffer)
|
}, new(bytes.Buffer)
|
||||||
err := json.NewEncoder(reqBodyBytes).Encode(reqBody)
|
err := json.NewEncoder(reqBodyBytes).Encode(reqBody)
|
||||||
if err != nil { return nil, err }
|
if err != nil { return nil, err }
|
||||||
req, _ := http.NewRequest(http.MethodPost, pdsUrl + "/xrpc/com.atproto.repo.createRecord", reqBodyBytes)
|
req, _ := http.NewRequest(http.MethodPost, session.PdsUrl + "/xrpc/com.atproto.repo.createRecord", reqBodyBytes)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("Authorization", "Bearer " + session.AccessJwt)
|
req.Header.Set("Authorization", "Bearer " + session.AccessJwt)
|
||||||
|
|
||||||
|
@ -175,7 +177,7 @@ func CreateAtprotoRecord(session *AtprotoSession, pdsUrl string, collection stri
|
||||||
return &createdRecord, err
|
return &createdRecord, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func PutAtprotoRecord(session *AtprotoSession, pdsUrl string, collection string, record any) (*AtprotoUriCid, error) {
|
func PutAtprotoRecord(session *AtprotoSession, collection string, record any) (*AtprotoUriCid, error) {
|
||||||
reqBody, reqBodyBytes := AtprotoPutRecord{
|
reqBody, reqBodyBytes := AtprotoPutRecord{
|
||||||
Repo: session.Did,
|
Repo: session.Did,
|
||||||
Collection: collection,
|
Collection: collection,
|
||||||
|
@ -185,7 +187,7 @@ func PutAtprotoRecord(session *AtprotoSession, pdsUrl string, collection string,
|
||||||
err := json.NewEncoder(reqBodyBytes).Encode(reqBody)
|
err := json.NewEncoder(reqBodyBytes).Encode(reqBody)
|
||||||
|
|
||||||
if err != nil { return nil, err }
|
if err != nil { return nil, err }
|
||||||
req, _ := http.NewRequest(http.MethodPost, pdsUrl + "/xrpc/com.atproto.repo.putRecord", reqBodyBytes)
|
req, _ := http.NewRequest(http.MethodPost, session.PdsUrl + "/xrpc/com.atproto.repo.putRecord", reqBodyBytes)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("Authorization", "Bearer " + session.AccessJwt)
|
req.Header.Set("Authorization", "Bearer " + session.AccessJwt)
|
||||||
|
|
||||||
|
@ -225,8 +227,8 @@ func GetAtprotoBlob(pdsUrl string, did string, cid string) ([]byte, error) {
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UploadAtprotoBlob(session *AtprotoSession, pdsUrl string, data []byte, mimeType string) (*AtprotoBlob, error) {
|
func UploadAtprotoBlob(session *AtprotoSession, data []byte, mimeType string) (*AtprotoBlob, error) {
|
||||||
req, _ := http.NewRequest(http.MethodPost, pdsUrl + "/xrpc/com.atproto.repo.uploadBlob", bytes.NewReader(data))
|
req, _ := http.NewRequest(http.MethodPost, session.PdsUrl + "/xrpc/com.atproto.repo.uploadBlob", bytes.NewReader(data))
|
||||||
req.Header.Set("Content-Type", mimeType)
|
req.Header.Set("Content-Type", mimeType)
|
||||||
req.Header.Set("Authorization", "Bearer " + session.AccessJwt)
|
req.Header.Set("Authorization", "Bearer " + session.AccessJwt)
|
||||||
|
|
||||||
|
|
60
funcs.go
60
funcs.go
|
@ -8,25 +8,15 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ImportProfile(session *AtprotoSession, pdsUrl string, fromAccount string, dryrun bool, verbose bool) error {
|
func ImportProfile(fromSession *AtprotoSession, toSession *AtprotoSession, dryrun bool, verbose bool) error {
|
||||||
fmt.Printf("fetching profile record from @%s...\n", fromAccount)
|
fmt.Printf("fetching profile record from @%s...\n", fromSession.Handle)
|
||||||
|
|
||||||
var err error
|
reqUrl, _ := url.Parse(fromSession.PdsUrl + "/xrpc/com.atproto.repo.getRecord")
|
||||||
fromDid := fromAccount
|
|
||||||
if !strings.HasPrefix(fromAccount, "did:") {
|
|
||||||
fromDid, err = ResolveAtprotoHandle(session, pdsUrl, fromAccount)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New(fmt.Sprintf("failed to resolve identity: %v\n", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reqUrl, _ := url.Parse(pdsUrl + "/xrpc/com.atproto.repo.getRecord")
|
|
||||||
reqUrl.RawQuery = url.Values{
|
reqUrl.RawQuery = url.Values{
|
||||||
"repo": { fromAccount },
|
"repo": { fromSession.Did },
|
||||||
"collection": { "app.bsky.actor.profile" },
|
"collection": { "app.bsky.actor.profile" },
|
||||||
"rkey": { "self" },
|
"rkey": { "self" },
|
||||||
}.Encode()
|
}.Encode()
|
||||||
|
@ -64,47 +54,47 @@ func ImportProfile(session *AtprotoSession, pdsUrl string, fromAccount string, d
|
||||||
|
|
||||||
if !dryrun {
|
if !dryrun {
|
||||||
// import avatar
|
// import avatar
|
||||||
avatarBytes, err := GetAtprotoBlob(pdsUrl, fromDid, profile.Avatar.Ref.Link)
|
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)) }
|
if err != nil { return errors.New(fmt.Sprintf("failed to download avatar: %v\n", err)) }
|
||||||
|
|
||||||
avatarBlob, err := UploadAtprotoBlob(session, pdsUrl, avatarBytes, profile.Avatar.MimeType)
|
avatarBlob, err := UploadAtprotoBlob(toSession, avatarBytes, profile.Avatar.MimeType)
|
||||||
if err != nil { return errors.New(fmt.Sprintf("failed to upload avatar: %v\n", err)) }
|
if err != nil { return errors.New(fmt.Sprintf("failed to upload avatar: %v\n", err)) }
|
||||||
newProfile.Avatar = avatarBlob
|
newProfile.Avatar = avatarBlob
|
||||||
|
|
||||||
// import banner
|
// import banner
|
||||||
bannerBytes, err := GetAtprotoBlob(pdsUrl, fromDid, profile.Banner.Ref.Link)
|
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)) }
|
if err != nil { return errors.New(fmt.Sprintf("failed to download banner: %v\n", err)) }
|
||||||
|
|
||||||
bannerBlob, err := UploadAtprotoBlob(session, pdsUrl, bannerBytes, profile.Banner.MimeType)
|
bannerBlob, err := UploadAtprotoBlob(toSession, bannerBytes, profile.Banner.MimeType)
|
||||||
if err != nil { return errors.New(fmt.Sprintf("failed to upload banner: %v\n", err)) }
|
if err != nil { return errors.New(fmt.Sprintf("failed to upload banner: %v\n", err)) }
|
||||||
newProfile.Banner = bannerBlob
|
newProfile.Banner = bannerBlob
|
||||||
|
|
||||||
// import all details
|
// import all details
|
||||||
_, err = PutAtprotoRecord(session, pdsUrl, "app.bsky.actor.profile", newProfile)
|
_, err = PutAtprotoRecord(toSession, "app.bsky.actor.profile", newProfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(fmt.Sprintf("failed to update profile record: %v\n", err))
|
return errors.New(fmt.Sprintf("failed to update profile record: %v\n", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Printf("profile imported from @%s successfully.\n", fromAccount)
|
fmt.Printf("profile imported from @%s to @%s successfully.\n", fromSession.Handle, toSession.Handle)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImportFollows(session *AtprotoSession, pdsUrl string, fromAccount string, dryrun bool, verbose bool) error {
|
func ImportFollows(fromSession *AtprotoSession, toSession *AtprotoSession, dryrun bool, verbose bool) error {
|
||||||
followDids := []string{}
|
followDids := []string{}
|
||||||
cursor := ""
|
cursor := ""
|
||||||
|
|
||||||
fmt.Printf("fetching follow records from @%s...\n", fromAccount)
|
fmt.Printf("fetching follow records from @%s...\n", fromSession.Handle)
|
||||||
for {
|
for {
|
||||||
reqUrl, _ := url.Parse(pdsUrl + "/xrpc/app.bsky.graph.getFollows")
|
reqUrl, _ := url.Parse(fromSession.PdsUrl + "/xrpc/app.bsky.graph.getFollows")
|
||||||
reqUrlValues := url.Values{
|
reqUrlValues := url.Values{
|
||||||
"actor": { fromAccount },
|
"actor": { fromSession.Did },
|
||||||
"limit": { "100" },
|
"limit": { "100" },
|
||||||
}
|
}
|
||||||
if cursor != "" { reqUrlValues.Set("cursor", cursor) }
|
if cursor != "" { reqUrlValues.Set("cursor", cursor) }
|
||||||
reqUrl.RawQuery = reqUrlValues.Encode()
|
reqUrl.RawQuery = reqUrlValues.Encode()
|
||||||
req, _ := http.NewRequest(http.MethodGet, reqUrl.String(), nil)
|
req, _ := http.NewRequest(http.MethodGet, reqUrl.String(), nil)
|
||||||
req.Header.Set("Authorization", "Bearer " + session.AccessJwt)
|
req.Header.Set("Authorization", "Bearer " + fromSession.AccessJwt)
|
||||||
|
|
||||||
res, err := http.DefaultClient.Do(req)
|
res, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -130,9 +120,9 @@ func ImportFollows(session *AtprotoSession, pdsUrl string, fromAccount string, d
|
||||||
}
|
}
|
||||||
fmt.Printf("fetched %d follow records.\n", len(followDids))
|
fmt.Printf("fetched %d follow records.\n", len(followDids))
|
||||||
|
|
||||||
fmt.Printf("importing follow records to @%s...\n", session.Handle)
|
fmt.Printf("importing follow records to @%s...\n", toSession.Handle)
|
||||||
successful := 0
|
successful := 0
|
||||||
for i, did := range followDids[len(followDids)-3:] {
|
for i, did := range followDids {
|
||||||
if dryrun {
|
if dryrun {
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Printf("(%d/%d) followed %s (dry run)\n", i, len(followDids), did)
|
fmt.Printf("(%d/%d) followed %s (dry run)\n", i, len(followDids), did)
|
||||||
|
@ -145,7 +135,7 @@ func ImportFollows(session *AtprotoSession, pdsUrl string, fromAccount string, d
|
||||||
CreatedAt: time.Now().Format(time.RFC3339),
|
CreatedAt: time.Now().Format(time.RFC3339),
|
||||||
}
|
}
|
||||||
recordJson, _ := json.Marshal(record)
|
recordJson, _ := json.Marshal(record)
|
||||||
newRecord, err := CreateAtprotoRecord(session, pdsUrl, "app.bsky.graph.follow", recordJson)
|
newRecord, err := CreateAtprotoRecord(toSession, "app.bsky.graph.follow", recordJson)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "warn: failed to create follow record for %s: %v\n", did, err)
|
fmt.Fprintf(os.Stderr, "warn: failed to create follow record for %s: %v\n", did, err)
|
||||||
continue
|
continue
|
||||||
|
@ -156,15 +146,21 @@ func ImportFollows(session *AtprotoSession, pdsUrl string, fromAccount string, d
|
||||||
successful += 1
|
successful += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%d/%d follow records imported successfully!\n", successful, len(followDids))
|
fmt.Printf(
|
||||||
|
"%d/%d follow records imported from @%s to @%s successfully!\n",
|
||||||
|
successful,
|
||||||
|
len(followDids),
|
||||||
|
fromSession.Handle,
|
||||||
|
toSession.Handle,
|
||||||
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImportPreferences(fromSession *AtprotoSession, toSession *AtprotoSession, pdsUrl string, dryrun bool, verbose bool) error {
|
func ImportPreferences(fromSession *AtprotoSession, toSession *AtprotoSession, dryrun bool, verbose bool) error {
|
||||||
fmt.Printf("fetching preferences from @%s...\n", fromSession.Handle)
|
fmt.Printf("fetching preferences from @%s...\n", fromSession.Handle)
|
||||||
|
|
||||||
req, _ := http.NewRequest(http.MethodGet, pdsUrl + "/xrpc/app.bsky.actor.getPreferences", nil)
|
req, _ := http.NewRequest(http.MethodGet, fromSession.PdsUrl + "/xrpc/app.bsky.actor.getPreferences", nil)
|
||||||
req.Header.Set("Authorization", "Bearer " + fromSession.AccessJwt)
|
req.Header.Set("Authorization", "Bearer " + fromSession.AccessJwt)
|
||||||
res, err := http.DefaultClient.Do(req)
|
res, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -182,7 +178,7 @@ func ImportPreferences(fromSession *AtprotoSession, toSession *AtprotoSession, p
|
||||||
fmt.Printf("importing preferences to @%s...\n", toSession.Handle)
|
fmt.Printf("importing preferences to @%s...\n", toSession.Handle)
|
||||||
if !dryrun {
|
if !dryrun {
|
||||||
reqBodyBytes, _ := json.Marshal(data)
|
reqBodyBytes, _ := json.Marshal(data)
|
||||||
req, _ := http.NewRequest(http.MethodPost, pdsUrl + "/xrpc/app.bsky.actor.putPreferences", bytes.NewReader(reqBodyBytes))
|
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("Content-Type", "application/json")
|
||||||
req.Header.Set("Authorization", "Bearer " + toSession.AccessJwt)
|
req.Header.Set("Authorization", "Bearer " + toSession.AccessJwt)
|
||||||
|
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,3 +1,3 @@
|
||||||
module arimelody.me/bluesky-follow-migrator
|
module arimelody.me/migratesky
|
||||||
|
|
||||||
go 1.24.4
|
go 1.24.4
|
||||||
|
|
61
main.go
61
main.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -21,16 +22,13 @@ func main() {
|
||||||
var dryrun bool = false
|
var dryrun bool = false
|
||||||
var verbose bool = false
|
var verbose bool = false
|
||||||
|
|
||||||
var importProfile bool = false
|
collections := []string{}
|
||||||
var importFollows bool = false
|
|
||||||
var importPreferences bool = false
|
|
||||||
|
|
||||||
i := 1
|
i := 1
|
||||||
for {
|
for {
|
||||||
if i >= len(os.Args) { break }
|
if i >= len(os.Args) { break }
|
||||||
if os.Args[i][0] != '-' {
|
if os.Args[i][0] != '-' {
|
||||||
fmt.Fprintf(os.Stderr, "unrecognised argument `%s`.\n", os.Args[i])
|
collections = append(collections, os.Args[i])
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue := func(i int) (int, string) {
|
getValue := func(i int) (int, string) {
|
||||||
|
@ -58,12 +56,6 @@ func main() {
|
||||||
dryrun = true
|
dryrun = true
|
||||||
case "v":
|
case "v":
|
||||||
verbose = true
|
verbose = true
|
||||||
case "profile":
|
|
||||||
importProfile = true
|
|
||||||
case "follows":
|
|
||||||
importFollows = true
|
|
||||||
case "preferences":
|
|
||||||
importPreferences = true
|
|
||||||
}
|
}
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
|
@ -72,12 +64,16 @@ func main() {
|
||||||
fmt.Fprintf(os.Stderr, "missing required argument -from.\n")
|
fmt.Fprintf(os.Stderr, "missing required argument -from.\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
if fromPassword == "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "missing required argument -frompass.\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
if toUser == "" {
|
if toUser == "" {
|
||||||
fmt.Fprintf(os.Stderr, "missing required argument -to.\n")
|
fmt.Fprintf(os.Stderr, "missing required argument -to.\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if toPassword == "" {
|
if toPassword == "" {
|
||||||
fmt.Fprintf(os.Stderr, "missing required argument -pass.\n")
|
fmt.Fprintf(os.Stderr, "missing required argument -topass.\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if pdsUrl == "" {
|
if pdsUrl == "" {
|
||||||
|
@ -86,19 +82,15 @@ func main() {
|
||||||
}
|
}
|
||||||
if fromPdsUrl == "" { fromPdsUrl = pdsUrl }
|
if fromPdsUrl == "" { fromPdsUrl = pdsUrl }
|
||||||
|
|
||||||
if !(importProfile || importFollows || importPreferences) {
|
if len(collections) == 0 {
|
||||||
fmt.Fprintf(os.Stderr, "no action was specified.\n")
|
fmt.Fprintf(os.Stderr, "no action was specified.\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fromSession *AtprotoSession
|
fromSession, err := CreateSession(fromPdsUrl, fromUser, fromPassword)
|
||||||
if !(fromPassword == "" || fromPdsUrl == "") {
|
if err != nil {
|
||||||
var err error
|
fmt.Fprintf(os.Stderr, "failed to create session for source account: %v\n", err)
|
||||||
fromSession, err = CreateSession(fromPdsUrl, fromUser, fromPassword)
|
os.Exit(1)
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "failed to create session for source account: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toSession, err := CreateSession(pdsUrl, toUser, toPassword)
|
toSession, err := CreateSession(pdsUrl, toUser, toPassword)
|
||||||
|
@ -107,26 +99,22 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if importProfile {
|
if slices.Contains(collections, "profile") {
|
||||||
err := ImportProfile(toSession, pdsUrl, fromUser, dryrun, verbose)
|
err := ImportProfile(fromSession, toSession, dryrun, verbose)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "failed to import profile: %v\n", err)
|
fmt.Fprintf(os.Stderr, "failed to import profile: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if importPreferences {
|
if slices.Contains(collections, "preferences") {
|
||||||
if fromSession == nil {
|
err := ImportPreferences(fromSession, toSession, dryrun, verbose)
|
||||||
fmt.Fprintf(os.Stderr, "-frompass and -frompds must be provided for a preferences import.\n")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
err := ImportPreferences(fromSession, toSession, pdsUrl, dryrun, verbose)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "failed to import preferences: %v\n", err)
|
fmt.Fprintf(os.Stderr, "failed to import preferences: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if importFollows {
|
if slices.Contains(collections, "follows") {
|
||||||
err := ImportFollows(toSession, pdsUrl, fromUser, dryrun, verbose)
|
err := ImportFollows(fromSession, toSession, dryrun, verbose)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "failed to import follows: %v\n", err)
|
fmt.Fprintf(os.Stderr, "failed to import follows: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -136,7 +124,7 @@ func main() {
|
||||||
|
|
||||||
func printHelp() {
|
func printHelp() {
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
`usage: %s <arguments...> <imports...>
|
`usage: %s <-from <identifier>> <-frompass <password>> <-to <identifier>> <-topass <identifier>> <-pds <pds-url>> [collections...]
|
||||||
|
|
||||||
required arguments:
|
required arguments:
|
||||||
-from <identifier>: the source account handle or DID.
|
-from <identifier>: the source account handle or DID.
|
||||||
|
@ -145,9 +133,10 @@ required arguments:
|
||||||
-topass <password>: the destination account password.
|
-topass <password>: the destination account password.
|
||||||
-pds <pds-url>: the full https:// url of the destination account's PDS.
|
-pds <pds-url>: the full https:// url of the destination account's PDS.
|
||||||
|
|
||||||
imports (at least one required):
|
collections:
|
||||||
-profile: imports the account profile, including avatar, banner, display name, and description.
|
profile: imports account profile, including avatar, banner, display name, and description.
|
||||||
-follows: imports the following list of the account.
|
follows: imports all following accounts.
|
||||||
|
preferences: imports account preferences; including feeds, labeller settings, and content filters.
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-frompds <pds-url>: the source account's PDS url (defaults to the value of -pds).
|
-frompds <pds-url>: the source account's PDS url (defaults to the value of -pds).
|
||||||
|
@ -155,6 +144,6 @@ optional arguments:
|
||||||
-v: verbose output
|
-v: verbose output
|
||||||
-help: shows this help message.
|
-help: shows this help message.
|
||||||
`,
|
`,
|
||||||
os.Args[0],
|
os.Args[0][strings.LastIndex(os.Args[0], "/") + 1:],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue