package main import ( "encoding/json" "errors" "fmt" "net/http" "net/url" "os" "strings" "time" ) func ImportProfile(session *AtprotoSession, pdsUrl string, fromAccount string, dryrun bool, verbose bool) error { fmt.Printf("fetching profile record from @%s...\n", fromAccount) var err error if !strings.HasPrefix(fromAccount, "did:") { fromAccount, 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{ "repo": { fromAccount }, "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) 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 { // import avatar avatarBytes, err := GetAtprotoBlob(pdsUrl, fromAccount, 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) if err != nil { return errors.New(fmt.Sprintf("failed to upload avatar: %v\n", err)) } newProfile.Avatar = avatarBlob // import banner bannerBytes, err := GetAtprotoBlob(pdsUrl, fromAccount, 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) if err != nil { return errors.New(fmt.Sprintf("failed to upload banner: %v\n", err)) } newProfile.Banner = bannerBlob // import all details _, err = PutAtprotoRecord(session, pdsUrl, "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 successfully.\n", fromAccount) return nil } func ImportFollows(session *AtprotoSession, pdsUrl string, fromAccount string, dryrun bool, verbose bool) error { followDids := []string{} cursor := "" fmt.Printf("fetching follow records from @%s...\n", fromAccount) for { reqUrl, _ := url.Parse(pdsUrl + "/xrpc/app.bsky.graph.getFollows") reqUrlValues := url.Values{ "actor": { fromAccount }, "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 " + session.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", session.Handle) successful := 0 for i, did := range followDids[len(followDids)-3:] { 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(session, pdsUrl, "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 successfully!\n", successful, len(followDids)) return nil }