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<!doctype html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8"/>
5 <link rel="icon" type="image/webp" href="/halloween_moover.webp"/>
6 <meta property="og:description" content="ATProto account migration tool"/>
7 <meta property="og:image" content="/halloween_moover.webp">
8 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
9 <title>PDS MOOver - Turn OFF</title>
10 <link rel="stylesheet" href="/style.css">
11 <script src="https://unpkg.com/alpinejs" defer></script>
12
13 <script type="module">
14 import {Migrator} from './src/main.js';
15
16 window.Migrator = new Migrator();
17 </script>
18
19 <script>
20 document.addEventListener('alpine:init', () => {
21
22 Alpine.data('moover', () => ({
23 oldHandle: '',
24 oldPassword: '',
25 twoFactorCode: '',
26 showTwoFactorCodeInput: false,
27 error: null,
28 showStatusMessage: false,
29 updateStatusHandler(status) {
30 console.log("Status update:", status);
31 document.getElementById("status-message").innerText = status;
32 },
33 async handleSubmit() {
34 this.error = null;
35 this.showStatusMessage = false;
36
37 try {
38
39 if (this.showTwoFactorCodeInput) {
40 if (this.twoFactorCode === null) {
41 this.error = 'Please enter the 2FA that was sent to your email.'
42 }
43 }
44
45 this.showStatusMessage = true;
46 await window.Migrator.deactivateOldAccount(
47 this.oldHandle,
48 this.oldPassword,
49 this.updateStatusHandler,
50 this.twoFactorCode);
51 } catch (error) {
52 console.error(error.error, error.message);
53 if (error.error === 'AuthFactorTokenRequired') {
54 this.showTwoFactorCodeInput = true;
55 }
56 this.error = error.message;
57 }
58 },
59 }))
60 })
61 </script>
62</head>
63<body>
64<div class="container" x-data="moover">
65 <h1>PDS MOOver</h1>
66
67 <div class="cow-image">
68 <img src="/halloween_moover.webp" alt="Cartoon milk cow" style="max-width: 100%; max-height: 100%; object-fit: contain;">
69 </div>
70 <div class="made-by-blur">Made by <a href="https://bsky.app/profile/baileytownsend.dev">@baileytownsend.dev</a>
71 </div>
72 <p>Use this page to make sure your old account is deactivated</p>
73 <form id="moover-form" @submit.prevent="await handleSubmit()">
74 <!-- First section: Login credentials -->
75 <div class="section">
76 <h2>Login for your old PDS</h2>
77 <div class="form-group">
78 <label for="handle">Old Handle:</label>
79 <input type="text" id="handle" name="handle" placeholder="alice.bsky.social" x-model="oldHandle"
80 required>
81 </div>
82
83 <div class="form-group">
84 <label for="password">Old Password:</label>
85 <input type="password" id="password" name="password" x-model="oldPassword" required>
86 </div>
87
88 <div x-show="showTwoFactorCodeInput" class="form-group">
89 <label for="two-factor-code">2FA from the email sent</label>
90 <input type="text" id="two-factor-code" name="two-factor-code" x-model="twoFactorCode">
91 <div class="error-message">Enter your 2fa code here</div>
92
93 </div>
94 </div>
95
96 <div x-show="error" x-text="error" class="error-message"></div>
97 <div x-show="showStatusMessage" id="status-message" class="status-message"></div>
98 <div>
99 <button type="submit">Turn it off</button>
100 </div>
101 </form>
102</div>
103
104
105</body>
106</html>