-
-
Save haileyok/67bede50387c3dc57448e716c0a9be6e to your computer and use it in GitHub Desktop.
import AtpAgent from '@atproto/api' | |
import { Secp256k1Keypair } from '@atproto/crypto' | |
import * as ui8 from 'uint8arrays' | |
const OLD_PDS_URL = 'https://bsky.social' | |
const NEW_PDS_URL = 'https://pds.haileyok.com' | |
const CURRENT_HANDLE = 'haileyok.com' | |
const CURRENT_PASSWORD = '' | |
const NEW_HANDLE = 'newphone.pds.haileyok.com' | |
const NEW_ACCOUNT_EMAIL = '' | |
const NEW_ACCOUNT_PASSWORD = '' | |
const NEW_PDS_INVITE_CODE = '' | |
const TOKEN = '' // Use `getEmail` first, and set this to the token that you receive. | |
// You probably need to bump your blob size if you're a real poster. | |
// Add to /pds/pds.env | |
// PDS_BLOB_UPLOAD_LIMIT=1000000000 | |
// Save the private key that's output below. If you trust your PDS opsec more, add the new key | |
// to the end (push). | |
// If you trust your own password manager more, put it in front [newKey, ...keys] | |
const getEmail = async () => { | |
const oldAgent = new AtpAgent.AtpAgent({ service: OLD_PDS_URL }) | |
await oldAgent.login({ | |
identifier: CURRENT_HANDLE, | |
password: CURRENT_PASSWORD, | |
}) | |
await oldAgent.com.atproto.identity.requestPlcOperationSignature() | |
} | |
const migrateAccount = async () => { | |
const oldAgent = new AtpAgent.AtpAgent({ service: OLD_PDS_URL }) | |
const newAgent = new AtpAgent.AtpAgent({ service: NEW_PDS_URL }) | |
await oldAgent.login({ | |
identifier: CURRENT_HANDLE, | |
password: CURRENT_PASSWORD, | |
}) | |
const accountDid = oldAgent.session?.did | |
if (!accountDid) { | |
throw new Error('Could not get DID for old account') | |
} | |
// Create account | |
// ------------------ | |
const describeRes = await newAgent.api.com.atproto.server.describeServer() | |
const newServerDid = describeRes.data.did | |
const serviceJwtRes = await oldAgent.com.atproto.server.getServiceAuth({ | |
aud: newServerDid, | |
}) | |
const serviceJwt = serviceJwtRes.data.token | |
await newAgent.api.com.atproto.server.createAccount( | |
{ | |
handle: NEW_HANDLE, | |
email: NEW_ACCOUNT_EMAIL, | |
password: NEW_ACCOUNT_PASSWORD, | |
did: accountDid, | |
inviteCode: NEW_PDS_INVITE_CODE, | |
}, | |
{ | |
headers: { authorization: `Bearer ${serviceJwt}` }, | |
encoding: 'application/json', | |
}, | |
) | |
await newAgent.login({ | |
identifier: NEW_HANDLE, | |
password: NEW_ACCOUNT_PASSWORD, | |
}) | |
// Migrate Data | |
// ------------------ | |
const repoRes = await oldAgent.com.atproto.sync.getRepo({ did: accountDid }) | |
await newAgent.com.atproto.repo.importRepo(repoRes.data, { | |
encoding: 'application/vnd.ipld.car', | |
}) | |
let blobCursor = undefined | |
do { | |
const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ | |
did: accountDid, | |
cursor: blobCursor, | |
}) | |
for (const cid of listedBlobs.data.cids) { | |
const blobRes = await oldAgent.com.atproto.sync.getBlob({ | |
did: accountDid, | |
cid, | |
}) | |
await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { | |
encoding: blobRes.headers['content-type'], | |
}) | |
} | |
blobCursor = listedBlobs.data.cursor | |
} while (blobCursor) | |
const prefs = await oldAgent.api.app.bsky.actor.getPreferences() | |
await newAgent.api.app.bsky.actor.putPreferences(prefs.data) | |
// Migrate Identity | |
// ------------------ | |
const getDidCredentials = | |
await newAgent.com.atproto.identity.getRecommendedDidCredentials() | |
console.log(JSON.stringify(getDidCredentials)) | |
// @NOTE, this token will need to come from the email from the previous step | |
const keypair = await Secp256k1Keypair.create({ exportable: true }) | |
const privateKey = await keypair.export() | |
const rotationKey = keypair.did() | |
console.log('SAVE THIS PRIVATE KEY!!!') | |
console.log('rotation key: ', rotationKey) | |
console.log('private key: ', ui8.toString(privateKey, 'hex')) | |
if(getDidCredentials.data.rotationKeys == null) { | |
console.log("Nope") | |
return | |
} | |
getDidCredentials.data.rotationKeys = [rotationKey, ...getDidCredentials.data.rotationKeys] | |
const plcOp = await oldAgent.com.atproto.identity.signPlcOperation({ | |
token: TOKEN, | |
...getDidCredentials.data, | |
}) | |
await newAgent.com.atproto.identity.submitPlcOperation({ | |
operation: plcOp.data.operation, | |
}) | |
// Finalize Migration | |
// ------------------ | |
await newAgent.com.atproto.server.activateAccount() | |
await oldAgent.com.atproto.server.deactivateAccount({}) | |
} | |
// STEP ONE IS HERE | |
getEmail() | |
// STEP TWO IS HERE. COMMENT OUT THE ABOVE AND UNCOMMENT THIS ONCE YOU HAVE YOUR CODE | |
// migrateAccount() |
fixed it!
Needs a couple of adjustments.
I changed import AtpAgent from '@atproto/api'
to import { AtpAgent } from '@atproto/api'
then adjusted all of the constructor references from new AtpAgent.AtpAgent(
to new AtpAgent(
.
I removed all the .api
parts of calls, for example oldAgent.api.app.bsky.actor.getPreferences()
changed to oldAgent.app.bsky.actor.getPreferences()
.
The last one is oldAgent.com.atproto.server.getServiceAuth
apparently needs lxm
provided with value com.atproto.server.createAccount
, see error: XRPCError: missing jwt lexicon method ("lxm"). must match: com.atproto.server.createAccount
.
Thanks for the script! It worked really well on a test account, working up the courage to throw the main account off the edge. 😮💨
oh yea, there's been a lot of changes to the api package since i made this...should probably update it lol
I don't think line 112 is supposed to be there lol