+1
-1
index.html
+1
-1
index.html
+70
-7
scanner.js
+70
-7
scanner.js
···
7
7
];
8
8
9
9
class URLError extends Error {}
10
+
class AccountError extends Error {}
11
+
class PostTakenDownError extends Error {}
10
12
11
13
document.addEventListener("DOMContentLoaded", function() {
12
14
initScanner();
···
102
104
resultField.innerText = '👾 Unable to resolve handle';
103
105
return;
104
106
}
105
-
} else if (error.json.error == 'AccountDeactivated') {
107
+
} else if (error.json.error == 'AccountDeactivated' || error.json.error == 'RepoDeactivated') {
106
108
resultField.innerText = '😶🌫️ Account is deactivated';
107
109
return;
110
+
} else if (error.json.error == 'RecordNotFound') {
111
+
resultField.innerText = '🚫 Post not found';
112
+
return;
113
+
} else if (error.json.error == 'RepoNotFound') {
114
+
resultField.innerText = '🚫 Account was deleted';
115
+
return;
108
116
}
109
117
}
110
118
···
112
120
return;
113
121
} else if (error instanceof URLError) {
114
122
resultField.innerText = `⚠️ ${error.message}`;
123
+
return;
124
+
} else if (error instanceof PostTakenDownError) {
125
+
resultField.innerText = `🚫 Post was taken down on the AppView`;
126
+
return;
127
+
} else if (error instanceof AccountError) {
128
+
resultField.innerText = '🚫 Account not found';
115
129
return;
116
130
}
117
131
···
140
154
}
141
155
142
156
async function scanURL(string) {
143
-
let atURI, note;
157
+
let atURI, note, userDID, rkey;
158
+
159
+
let match = string.match(/^at:\/\/(did:[^/]+)\/app\.bsky\.feed\.post\/([\w]+)$/);
144
160
145
-
if (string.match(/^at:\/\/did:[^/]+\/app\.bsky\.feed\.post\/[\w]+$/)) {
161
+
if (match) {
146
162
atURI = string;
163
+
userDID = match[1];
164
+
rkey = match[2];
147
165
} else {
148
166
let url = new URL(string);
149
167
···
167
185
let match = url.pathname.match(/^\/profile\/([^/]+)\/post\/([\w]+)\/?$/);
168
186
169
187
if (match && match[1].startsWith('did:')) {
170
-
atURI = `at://${match[1]}/app.bsky.feed.post/${match[2]}`;
188
+
userDID = match[1];
189
+
rkey = match[2];
171
190
} else if (match) {
172
191
let json = await appView.getRequest('com.atproto.identity.resolveHandle', { handle: match[1] });
173
-
atURI = `at://${json.did}/app.bsky.feed.post/${match[2]}`;
192
+
userDID = json.did;
193
+
rkey = match[2];
174
194
} else {
175
195
throw new URLError('Unknown URL');
176
196
}
197
+
198
+
atURI = `at://${userDID}/app.bsky.feed.post/${rkey}`;
177
199
}
178
200
}
179
201
180
-
let userDID = atURI.split('/')[2];
181
202
let batches = [];
182
203
183
204
for (let i = 0; i < labellers.length; i += batchSize) {
···
188
209
let results = await Promise.all(batches);
189
210
190
211
if (results.every(x => !x)) {
191
-
throw '🚫 Post not found.';
212
+
// post not found, look it up on the origin PDS
213
+
214
+
let post = await loadPostFromPDS(userDID, rkey);
215
+
216
+
// post found, so it was taken down on the AppView
217
+
218
+
throw new PostTakenDownError();
192
219
}
193
220
194
221
let labels = results.flatMap(x => x.labels).filter(x => (x.src != userDID));
195
222
return { labels, note };
223
+
}
224
+
225
+
async function loadPostFromPDS(did, rkey) {
226
+
let didDocument = await fetchDidDocument(did);
227
+
228
+
if (didDocument.message?.startsWith("DID not registered:")) {
229
+
throw new AccountError("Account not found");
230
+
}
231
+
232
+
let pds = didDocument.service?.find(x => x.id == "#atproto_pds")?.serviceEndpoint;
233
+
234
+
if (!pds) {
235
+
throw new AccountError("Invalid DID document");
236
+
}
237
+
238
+
let pdsSky = new Minisky(pds);
239
+
let repo = await pdsSky.getRequest('com.atproto.repo.describeRepo', { repo: did });
240
+
241
+
return await pdsSky.getRequest('com.atproto.repo.getRecord', {
242
+
repo: did,
243
+
collection: 'app.bsky.feed.post',
244
+
rkey: rkey
245
+
});
246
+
}
247
+
248
+
async function fetchDidDocument(did) {
249
+
if (did.startsWith('did:plc:')) {
250
+
let response = await fetch(`https://plc.directory/${did}`);
251
+
return await response.json();
252
+
} else if (did.startsWith('did:web:')) {
253
+
let hostname = did.split(':')[2];
254
+
let response = await fetch(`https://${hostname}/.well-known/did.json`);
255
+
return await response.json();
256
+
} else {
257
+
throw new AccountError("DID not found");
258
+
}
196
259
}
197
260
198
261
async function checkProfileWithLabellers(handle, batch) {