Client side atproto account migrator in your web browser, along with services for backups and adversarial migrations.
pdsmoover.com
pds
atproto
migrations
moo
cow
1//I need to condense this code with the rest of PDS MOOver cause it has a lot of overlap
2import {AtpAgent} from '@atproto/api';
3import {handleAndPDSResolver} from './atprotoUtils.js';
4
5
6class MissingBlobs {
7
8 constructor() {
9 this.currentPdsAgent = null;
10 this.oldPdsAgent = null;
11 this.did = null;
12 this.currentPdsUrl = null;
13 this.missingBlobs = [];
14
15 }
16
17 async currentAgentLogin(
18 handle,
19 password,
20 twoFactorCode = null,
21 ) {
22 let {usersDid, pds} = await handleAndPDSResolver(handle);
23 this.did = usersDid;
24 this.currentPdsUrl = pds;
25 const agent = new AtpAgent({
26 service: pds,
27 });
28
29 if (twoFactorCode === null) {
30 await agent.login({identifier: usersDid, password});
31 } else {
32 await agent.login({identifier: usersDid, password: password, authFactorToken: twoFactorCode});
33 }
34
35 this.currentPdsAgent = agent;
36
37 const result = await agent.com.atproto.server.checkAccountStatus();
38 const missingBlobs = await this.currentPdsAgent.com.atproto.repo.listMissingBlobs({
39 limit: 10,
40 });
41 return {accountStatus: result.data, missingBlobsCount: missingBlobs.data.blobs.length};
42 }
43
44 async oldAgentLogin(
45 password,
46 twoFactorCode = null,
47 pdsUrl = null,
48 ) {
49 let oldPds = null;
50
51 if (pdsUrl === null) {
52 const response = await fetch(`https://plc.directory/${this.did}/log`);
53 let auditLog = await response.json();
54 auditLog = auditLog.reverse();
55 let debugCount = 0;
56 for (const entry of auditLog) {
57 console.log(`Loop: ${debugCount++}`);
58 console.log(entry);
59 if (entry.services) {
60 if (entry.services.atproto_pds) {
61 if (entry.services.atproto_pds.type === 'AtprotoPersonalDataServer') {
62 const pds = entry.services.atproto_pds.endpoint;
63 console.log(`Found PDS: ${pds}`);
64 if (pds.toLowerCase() !== this.currentPdsUrl.toLowerCase()) {
65 oldPds = pds;
66 break;
67 }
68 }
69 }
70 }
71 }
72 if (oldPds === null) {
73 throw new Error('Could not find your old PDS');
74 }
75 } else {
76 oldPds = pdsUrl;
77 }
78
79 const agent = new AtpAgent({
80 service: oldPds,
81 });
82
83 if (twoFactorCode === null) {
84 await agent.login({identifier: this.did, password});
85 } else {
86 await agent.login({identifier: this.did, password: password, authFactorToken: twoFactorCode});
87 }
88 this.oldPdsAgent = agent;
89 }
90
91 async migrateMissingBlobs(statusUpdateHandler) {
92 if (this.currentPdsAgent === null) {
93 throw new Error('Current PDS agent is not set');
94 }
95 if (this.oldPdsAgent === null) {
96 throw new Error('Old PDS agent is not set');
97 }
98 statusUpdateHandler('Starting to import blobs...');
99
100 // const currentAccountStatus = await this.currentPdsAgent.com.atproto.server.checkAccountStatus();
101 // let totalMissingBlobs = currentAccountStatus.data.expectedBlobs - currentAccountStatus.data.importedBlobs;
102 let totalMissingBlobs = 0;
103 let missingBlobCursor = undefined;
104 let missingUploadedBlobs = 0;
105
106 do {
107
108 const missingBlobs = await this.currentPdsAgent.com.atproto.repo.listMissingBlobs({
109 cursor: missingBlobCursor,
110 //Test this cause it may be a big update
111 limit: 1000,
112 });
113 totalMissingBlobs += missingBlobs.data.blobs.length;
114
115 for (const recordBlob of missingBlobs.data.blobs) {
116 try {
117
118 const blobRes = await this.oldPdsAgent.com.atproto.sync.getBlob({
119 did: this.did,
120 cid: recordBlob.cid,
121 });
122 let result = await this.currentPdsAgent.com.atproto.repo.uploadBlob(blobRes.data, {
123 encoding: blobRes.headers['content-type'],
124 });
125
126 if (result.status === 429) {
127 statusUpdateHandler(`You are being rate limited. Will need to try again later to get the rest of the blobs. Migrated blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`);
128 }
129
130 if (missingUploadedBlobs % 2 === 0) {
131 statusUpdateHandler(`Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs} (The total may increase as we find more)`);
132 }
133 missingUploadedBlobs++;
134 } catch (error) {
135 console.error(error);
136 this.missingBlobs.push(recordBlob.cid);
137 }
138 }
139 missingBlobCursor = missingBlobs.data.cursor;
140 } while (missingBlobCursor);
141
142 const accountStatus = await this.currentPdsAgent.com.atproto.server.checkAccountStatus();
143 const missingBlobs = await this.currentPdsAgent.com.atproto.repo.listMissingBlobs({
144 limit: 10,
145 });
146 return {accountStatus: accountStatus.data, missingBlobsCount: missingBlobs.data.blobs.length};
147
148
149 }
150
151}
152
153export {MissingBlobs};