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{%- import "partials/cow-header.askama.html" as cow -%}
2
3{% extends "layout.askama.html" %}
4
5{% block meta %}
6 <meta property="og:description" content="Import missing blobs from your old PDS to your new PDS"/>
7 <meta property="og:image" content="/missing.webp">
8{% endblock %}
9
10{% block content %}
11
12<script>
13
14 document.addEventListener('alpine:init', () => {
15 window.Alpine.data('app', () => ({
16 showCurrentLogin: true,
17 showOldLogin: false,
18 showAdvance: false,
19 disableLoginButton: false,
20 showBlobMoveProgress: false,
21 oldPdsUrl: null,
22 showTryAgain: false,
23 error: '',
24 showStatusMessage: false,
25 statusMessage: '',
26 currentLogin: {
27 handle: '',
28 password: '',
29 twoFactorCode: '',
30 showTwoFactorCodeInput: false,
31 },
32 oldLogin: {
33 password: '',
34 twoFactorCode: '',
35 showTwoFactorCodeInput: false,
36 },
37 resetStatusAndErrors() {
38 this.showStatusMessage = false;
39 this.statusMessage = '';
40 this.error = '';
41 this.disableLoginButton = true;
42 },
43 async handleCurrentLogin() {
44 this.resetStatusAndErrors();
45 try {
46 const {
47 accountStatus,
48 missingBlobsCount
49 } = await window.MissingBlobs.currentAgentLogin(this.currentLogin.handle, this.currentLogin.password, this.currentLogin.twoFactorCode);
50 // const noMissingBlobs = accountStatus.expectedBlobs <= accountStatus.importedBlobs;
51 console.log(missingBlobsCount);
52 const noMissingBlobs = missingBlobsCount === 0;
53 if (noMissingBlobs) {
54 this.statusMessage = `You are good to go! You are not missing any blobs. Your account has ${accountStatus.importedBlobs} imported blobs and expects to have at least ${accountStatus.expectedBlobs} blobs. No action is required.`
55 } else {
56 this.showCurrentLogin = false;
57 this.statusMessage = `You are currently missing some blobs. Login with your old password to import the missing blobs. We will automatically find your old handle.`;
58 this.showOldLogin = true;
59 }
60 this.showStatusMessage = true;
61
62 } catch (error) {
63 console.error(error.error, error.message);
64 if (error.error === 'AuthFactorTokenRequired') {
65 this.currentLogin.showTwoFactorCodeInput = true;
66 }
67 this.error = error.message;
68 }
69 this.disableLoginButton = false;
70 },
71 async handleOldLogin() {
72 this.resetStatusAndErrors();
73 try {
74 await window.MissingBlobs.oldAgentLogin(this.oldLogin.password, this.oldLogin.twoFactorCode, this.oldPdsUrl);
75 this.showOldLogin = false;
76 this.showBlobMoveProgress = true;
77 this.showStatusMessage = true;
78 this.statusMessage = '';
79 await this.migrateMissingBlobs();
80 } catch (error) {
81 console.error(error.error, error.message);
82 if (error.error === 'AuthFactorTokenRequired') {
83 this.oldLogin.showTwoFactorCodeInput = true;
84 }
85 this.error = error.message;
86 }
87 this.disableLoginButton = false;
88 },
89 updateStatusHandler(status) {
90 console.log("Status update:", status);
91 document.getElementById("missing-status-message").innerText = status;
92
93 },
94 async migrateMissingBlobs() {
95 try {
96 this.resetStatusAndErrors();
97 this.showStatusMessage = true;
98 this.showTryAgain = false;
99 const {
100 accountStatus,
101 missingBlobsCount
102 } = await window.MissingBlobs.migrateMissingBlobs(this.updateStatusHandler);
103 const noMissingBlobs = missingBlobsCount === 0;
104 // const noMissingBlobs = accountStatus.expectedBlobs === accountStatus.importedBlobs;
105 if (noMissingBlobs) {
106 this.statusMessage = `You are good to go! You have all ${accountStatus.importedBlobs} of the expected ${accountStatus.expectedBlobs} blobs. You're done!!`
107 } else {
108 this.statusMessage = `Expected blobs: ${accountStatus.expectedBlobs} Imported blobs: ${accountStatus.importedBlobs}`;
109 this.showTryAgain = true;
110 }
111 } catch (error) {
112 console.error(error.error, error.message);
113 this.error = error.message;
114 this.showTryAgain = true;
115 }
116 this.disableLoginButton = false;
117 },
118 toggleAdvanceMenu() {
119 this.showAdvance = !this.showAdvance;
120 }
121
122 }));
123 });
124 </script>
125
126</head>
127<body>
128<div class="container" x-data="app">
129
130
131
132 {% call cow::cow_header("Missing Blobs Importer", "<img src='/missing.webp' alt='Cartoon milk cow on a missing poster' style='max-width: 100%; max-height: 100%; object-fit: contain;'>") %}
133
134 <a href="https://blacksky.community/profile/did:plc:g7j6qok5us4hjqlwjxwrrkjm/post/3lyylumcpok2c">How to video
135 guide</a>
136
137
138 <!-- First section: Current Login credentials -->
139
140 <form x-show="showCurrentLogin" id="moover-form" @submit.prevent="await handleCurrentLogin()">
141 <div class="section">
142 <h2>Login for your current PDS</h2>
143 <div class="form-group">
144 <label for="current_handle">Current Handle:</label>
145 <input type="text" id="current_handle" name="handle" placeholder="alice.bsky.social"
146 x-model="currentLogin.handle"
147 required>
148 </div>
149
150 <div class="form-group">
151 <label for="current_password">Current Password:</label>
152 <input type="password" id="current_password" name="password" x-model="currentLogin.password" required>
153 </div>
154
155 <div x-show="currentLogin.showTwoFactorCodeInput" class="form-group">
156 <label for="current_two-factor-code">2FA from the email sent</label>
157 <input type="text" id="current_two-factor-code" name="two-factor-code"
158 x-model="currentLogin.twoFactorCode">
159 <div class="error-message">Enter your 2fa code here</div>
160 </div>
161 <div x-show="error" x-text="error" class="error-message"></div>
162 <div x-show="showStatusMessage" x-text="statusMessage" class="status-message"></div>
163
164 <div>
165 <button x-bind:disabled="disableLoginButton" type="submit">Login</button>
166 </div>
167 </div>
168 </form>
169
170 <!-- Second section: Old Login credentials -->
171
172 <form x-show="showOldLogin" @submit.prevent="await handleOldLogin()">
173 <div class="section">
174 <h2>Password for your OLD PDS</h2>
175 <p>We only need your password for your old account. We can find your old handle from your current login.</p>
176 <div class="form-group">
177 <label for="password">OLD Password:</label>
178 <input type="password" id="password" name="password" x-model="oldLogin.password" required>
179 </div>
180
181 <div x-show="oldLogin.showTwoFactorCodeInput" class="form-group">
182 <label for="two-factor-code">2FA from the email sent</label>
183 <input type="text" id="two-factor-code" name="two-factor-code" x-model="oldLogin.twoFactorCode">
184 <div class="error-message">Enter your 2fa code here</div>
185 </div>
186
187 <div x-show="showAdvance" class="form-group show-advance">
188 <label for="old_pds">This is optional. If you do not know your old PDS url please leave it blank. We
189 will find it for you. </label>
190 <input type="url" id="old_pds" name="two-factor-code"
191 placeholder="(Optional) Your old PDS URL" x-model="oldPdsUrl">
192 </div>
193 <div class="form-group">
194 <button type="button" @click="toggleAdvanceMenu()" id="advance" name="advance">Advance Options
195 </button>
196 </div>
197
198
199 <div x-show="error" x-text="error" class="error-message"></div>
200 <div x-show="showStatusMessage" x-text="statusMessage" class="status-message"></div>
201
202 <div>
203 <button x-bind:disabled="disableLoginButton" type="submit">Login and start the import of missing blobs
204 </button>
205 </div>
206 </div>
207 </form>
208
209 <!-- Third section: Progress while uploading blobs-->
210
211 <div x-show="showBlobMoveProgress">
212 <div x-show="showStatusMessage" id="warning">*This will take a while. Please do not close this tab. And watch
213 the status message below for updates
214 </div>
215 <div x-show="showStatusMessage" x-text="statusMessage" id="missing-status-message" class="status-message"></div>
216 <div x-show="error" x-text="error" class="error-message"></div>
217 <div x-show="showTryAgain">
218 <p style="color: yellow">We were unable to import all of your previous blobs, please try again. If it is
219 still
220 not completing give
221 it a few hours and come back and try again. It may be rate limited. Re running this tool does not harm
222 your account.</p>
223 <br>
224 <button x-on:click="await migrateMissingBlobs()">Try again</button>
225 </div>
226
227
228 </div>
229
230
231</div>
232
233{% endblock %}