add import preferences (-preferences)
This commit is contained in:
parent
24db172462
commit
6bde84b7e3
3 changed files with 107 additions and 28 deletions
15
atproto.go
15
atproto.go
|
@ -43,7 +43,7 @@ type (
|
|||
Rkey string `json:"rkey"`
|
||||
Record any `json:"record"`
|
||||
}
|
||||
AtprotoUidCid struct {
|
||||
AtprotoUriCid struct {
|
||||
Uri string `json:"uri"`
|
||||
Cid string `json:"cid"`
|
||||
}
|
||||
|
@ -87,7 +87,10 @@ type (
|
|||
Avatar *AtprotoBlob `json:"avatar,omitempty"`
|
||||
Banner *AtprotoBlob `json:"banner,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
PinnedPost *AtprotoUidCid `json:"pinnedPost,omitempty"`
|
||||
PinnedPost *AtprotoUriCid `json:"pinnedPost,omitempty"`
|
||||
}
|
||||
BskyActorPreferences struct {
|
||||
Preferences []json.RawMessage `json:"preferences"`
|
||||
}
|
||||
BskyGraphFollow struct {
|
||||
LexiconTypeID string `json:"$type,const=app.bsky.graph.follow"`
|
||||
|
@ -148,7 +151,7 @@ func ResolveAtprotoHandle(session *AtprotoSession, pdsUrl string, handle string)
|
|||
return identity.Did, nil
|
||||
}
|
||||
|
||||
func CreateAtprotoRecord(session *AtprotoSession, pdsUrl string, collection string, record json.RawMessage) (*AtprotoUidCid, error) {
|
||||
func CreateAtprotoRecord(session *AtprotoSession, pdsUrl string, collection string, record json.RawMessage) (*AtprotoUriCid, error) {
|
||||
reqBody, reqBodyBytes := AtprotoCreateRecord{
|
||||
Repo: session.Did,
|
||||
Collection: collection,
|
||||
|
@ -167,12 +170,12 @@ func CreateAtprotoRecord(session *AtprotoSession, pdsUrl string, collection stri
|
|||
json.NewDecoder(res.Body).Decode(&errBody)
|
||||
return nil, errors.New(fmt.Sprintf("%s: %s", errBody.Error, errBody.Message))
|
||||
}
|
||||
createdRecord := AtprotoUidCid{}
|
||||
createdRecord := AtprotoUriCid{}
|
||||
err = json.NewDecoder(res.Body).Decode(&createdRecord)
|
||||
return &createdRecord, err
|
||||
}
|
||||
|
||||
func PutAtprotoRecord(session *AtprotoSession, pdsUrl string, collection string, record any) (*AtprotoUidCid, error) {
|
||||
func PutAtprotoRecord(session *AtprotoSession, pdsUrl string, collection string, record any) (*AtprotoUriCid, error) {
|
||||
reqBody, reqBodyBytes := AtprotoPutRecord{
|
||||
Repo: session.Did,
|
||||
Collection: collection,
|
||||
|
@ -193,7 +196,7 @@ func PutAtprotoRecord(session *AtprotoSession, pdsUrl string, collection string,
|
|||
json.NewDecoder(res.Body).Decode(&errBody)
|
||||
return nil, errors.New(fmt.Sprintf("%s: %s", errBody.Error, errBody.Message))
|
||||
}
|
||||
updatedRecord := AtprotoUidCid{}
|
||||
updatedRecord := AtprotoUriCid{}
|
||||
err = json.NewDecoder(res.Body).Decode(&updatedRecord)
|
||||
return &updatedRecord, err
|
||||
}
|
||||
|
|
49
funcs.go
49
funcs.go
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -15,8 +16,9 @@ func ImportProfile(session *AtprotoSession, pdsUrl string, fromAccount string, d
|
|||
fmt.Printf("fetching profile record from @%s...\n", fromAccount)
|
||||
|
||||
var err error
|
||||
fromDid := fromAccount
|
||||
if !strings.HasPrefix(fromAccount, "did:") {
|
||||
fromAccount, err = ResolveAtprotoHandle(session, pdsUrl, fromAccount)
|
||||
fromDid, err = ResolveAtprotoHandle(session, pdsUrl, fromAccount)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("failed to resolve identity: %v\n", err))
|
||||
}
|
||||
|
@ -62,7 +64,7 @@ func ImportProfile(session *AtprotoSession, pdsUrl string, fromAccount string, d
|
|||
|
||||
if !dryrun {
|
||||
// import avatar
|
||||
avatarBytes, err := GetAtprotoBlob(pdsUrl, fromAccount, profile.Avatar.Ref.Link)
|
||||
avatarBytes, err := GetAtprotoBlob(pdsUrl, fromDid, profile.Avatar.Ref.Link)
|
||||
if err != nil { return errors.New(fmt.Sprintf("failed to download avatar: %v\n", err)) }
|
||||
|
||||
avatarBlob, err := UploadAtprotoBlob(session, pdsUrl, avatarBytes, profile.Avatar.MimeType)
|
||||
|
@ -70,7 +72,7 @@ func ImportProfile(session *AtprotoSession, pdsUrl string, fromAccount string, d
|
|||
newProfile.Avatar = avatarBlob
|
||||
|
||||
// import banner
|
||||
bannerBytes, err := GetAtprotoBlob(pdsUrl, fromAccount, profile.Banner.Ref.Link)
|
||||
bannerBytes, err := GetAtprotoBlob(pdsUrl, fromDid, profile.Banner.Ref.Link)
|
||||
if err != nil { return errors.New(fmt.Sprintf("failed to download banner: %v\n", err)) }
|
||||
|
||||
bannerBlob, err := UploadAtprotoBlob(session, pdsUrl, bannerBytes, profile.Banner.MimeType)
|
||||
|
@ -159,3 +161,44 @@ func ImportFollows(session *AtprotoSession, pdsUrl string, fromAccount string, d
|
|||
return nil
|
||||
}
|
||||
|
||||
func ImportPreferences(fromSession *AtprotoSession, toSession *AtprotoSession, pdsUrl string, dryrun bool, verbose bool) error {
|
||||
fmt.Printf("fetching preferences from @%s...\n", fromSession.Handle)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, 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, 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
|
||||
}
|
||||
|
|
71
main.go
71
main.go
|
@ -12,15 +12,18 @@ func main() {
|
|||
os.Exit(0)
|
||||
}
|
||||
|
||||
var srcUser string
|
||||
var destUser string
|
||||
var password string
|
||||
var fromUser string
|
||||
var fromPassword string
|
||||
var toUser string
|
||||
var toPassword string
|
||||
var pdsUrl string
|
||||
var fromPdsUrl string
|
||||
var dryrun bool = false
|
||||
var verbose bool = false
|
||||
|
||||
var importProfile bool = false
|
||||
var importFollows bool = false
|
||||
var importPreferences bool = false
|
||||
|
||||
i := 1
|
||||
for {
|
||||
|
@ -40,13 +43,17 @@ func main() {
|
|||
|
||||
switch os.Args[i][1:] {
|
||||
case "from":
|
||||
i, srcUser = getValue(i)
|
||||
i, fromUser = getValue(i)
|
||||
case "frompass":
|
||||
i, fromPassword = getValue(i)
|
||||
case "to":
|
||||
i, destUser = getValue(i)
|
||||
case "pass":
|
||||
i, password = getValue(i)
|
||||
i, toUser = getValue(i)
|
||||
case "topass":
|
||||
i, toPassword = getValue(i)
|
||||
case "pds":
|
||||
i, pdsUrl = getValue(i)
|
||||
case "frompds":
|
||||
i, fromPdsUrl = getValue(i)
|
||||
case "dryrun":
|
||||
dryrun = true
|
||||
case "v":
|
||||
|
@ -55,19 +62,21 @@ func main() {
|
|||
importProfile = true
|
||||
case "follows":
|
||||
importFollows = true
|
||||
case "preferences":
|
||||
importPreferences = true
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
||||
if srcUser == "" {
|
||||
if fromUser == "" {
|
||||
fmt.Fprintf(os.Stderr, "missing required argument -from.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
if destUser == "" {
|
||||
if toUser == "" {
|
||||
fmt.Fprintf(os.Stderr, "missing required argument -to.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
if password == "" {
|
||||
if toPassword == "" {
|
||||
fmt.Fprintf(os.Stderr, "missing required argument -pass.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
@ -75,27 +84,49 @@ func main() {
|
|||
fmt.Fprintf(os.Stderr, "missing required argument -pds.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
if fromPdsUrl == "" { fromPdsUrl = pdsUrl }
|
||||
|
||||
if !importProfile && !importFollows {
|
||||
if !(importProfile || importFollows || importPreferences) {
|
||||
fmt.Fprintf(os.Stderr, "no action was specified.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
session, err := CreateSession(pdsUrl, destUser, password)
|
||||
var fromSession *AtprotoSession
|
||||
if !(fromPassword == "" || fromPdsUrl == "") {
|
||||
var err error
|
||||
fromSession, err = CreateSession(fromPdsUrl, fromUser, fromPassword)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to create session: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "failed to create session for source account: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
toSession, err := CreateSession(pdsUrl, toUser, toPassword)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to create session for destination account: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if importProfile {
|
||||
err := ImportProfile(session, pdsUrl, srcUser, dryrun, verbose)
|
||||
err := ImportProfile(toSession, pdsUrl, fromUser, dryrun, verbose)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to import profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
if importPreferences {
|
||||
if fromSession == nil {
|
||||
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 {
|
||||
fmt.Fprintf(os.Stderr, "failed to import preferences: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
if importFollows {
|
||||
err := ImportFollows(session, pdsUrl, srcUser, dryrun, verbose)
|
||||
err := ImportFollows(toSession, pdsUrl, fromUser, dryrun, verbose)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to import follows: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -108,16 +139,18 @@ func printHelp() {
|
|||
`usage: %s <arguments...> <imports...>
|
||||
|
||||
required arguments:
|
||||
-from <handle>: the account to import follow records from.
|
||||
-to <handle>: the account to import follow records to.
|
||||
-pass <password>: the password of the `+"`to`"+` account.
|
||||
-pds <pds-url>: the full https:// url of the `+"`to`"+` account's PDS.
|
||||
-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.
|
||||
|
||||
imports (at least one required):
|
||||
-profile: imports the account profile, including avatar, banner, display name, and description.
|
||||
-follows: imports the following list of the account.
|
||||
|
||||
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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue