bsky-scripts/appviewsync.mjs
2025-01-30 17:01:45 -08:00

250 lines
7.1 KiB
JavaScript

import { iterateAtpRepo } from "@atcute/car";
import * as fs from "fs";
var did = "did:plc:u4gngygg2w5egsigxu5g7byu";
var pds = "https://pds.owo69.me";
var identifier = "lamp.bsky.social";
var password = "";
async function fetch(url, options) {
console.debug("fetch", url);
var res = await global.fetch(url, options);
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText} ${await res.text()}`);
return res;
}
//try {
//var repo = fs.readFileSync("repo");
//} catch (e) {
var repo = await fetch(`${pds}/xrpc/com.atproto.sync.getRepo?did=${did}`).then(res => res.arrayBuffer());
repo = new Uint8Array(repo);
fs.writeFileSync("repo", repo);
//}
var [likes, posts, reposts] = [[],[],[]];
for (let {collection, rkey, record} of iterateAtpRepo(repo)) {
switch (collection) {
case "app.bsky.feed.like":
likes.push([rkey,record]);
break;
case "app.bsky.feed.post":
posts.push([rkey,record]);
break;
case "app.bsky.feed.repost":
reposts.push([rkey,record]);
break;
}
}
console.log(`repo: ${likes.length} likes ${posts.length} posts ${reposts.length} reposts`);
var res = await fetch(pds+"/xrpc/com.atproto.server.createSession", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({identifier, password})
});
var {accessJwt} = await res.json();
console.log(accessJwt);
console.log("getting likes");
var applikes = [];
var cursor = undefined;
do {
let res = await fetch(`${pds}/xrpc/app.bsky.feed.getActorLikes?actor=${did}&limit=100${cursor?`&cursor=${cursor}`:''}`, {
headers: {"Authorization": `Bearer ${accessJwt}`}
});
let j = await res.json();
for (let post of j.feed) {
//fs.appendFileSync("likes.txt", JSON.stringify(post) + ",\n");
applikes.push(post.post.viewer.like);
}
if (cursor == j.cursor) break;
cursor = j.cursor;
} while (cursor);
/*var appposts = [];
var posturis = posts.map(([rkey]) => `at://${did}/app.bsky.feed.post/${rkey}`);
for (let i = 0; i < posturis.length; i += 25) {
let s = posturis.slice(i, i + 25);
let q = posturis.map(u => `uris=${encodeURIComponent(u)}`).join('&');
let res = await fetch(`${pds}/xrpc/app.bsky.feed.getPosts?${q}`, {
headers: {"Authorization": `Bearer ${accessJwt}`}
});
console.log(res.status);
let j = await res.json();
for (let post of j.posts) {
appposts.push(post.uri);
fs.appendFileSync("posts.txt", JSON.stringify(post) + ",\n");
}
}*/
console.log("getting posts");
var appposts = [];
var appreposts = [];
var cursor = undefined;
do {
// posts_with_replies gets everything??
let res = await fetch(`${pds}/xrpc/app.bsky.feed.getAuthorFeed?actor=${did}&limit=100&filter=posts_with_replies${cursor?`&cursor=${cursor}`:''}`, {
headers: {"Authorization": `Bearer ${accessJwt}`}
});
let j = await res.json();
for (let post of j.feed) {
let isRepost = post.reason?.$type == "app.bsky.feed.defs#reasonRepost";
if (isRepost) {
//fs.appendFileSync("reposts.txt", JSON.stringify(post) + ",\n");
appreposts.push(post.post.viewer.repost);
} else {
//fs.appendFileSync("posts.txt", JSON.stringify(post) + ",\n");
appposts.push(post.post.uri);
}
}
if (cursor == j.cursor) break;
cursor = j.cursor;
} while (cursor);
console.log(`app: ${applikes.length} likes, ${appposts.length} posts, ${appreposts.length} reposts`);
var applikes = applikes.map(x => x.split('/').at(-1));
var appposts = appposts.map(x => x.split('/').at(-1));
var appreposts = appreposts.map(x => x.split('/').at(-1));
var missing = {likes: [], posts: [], reposts: []};
for (let [rkey, record] of likes) {
if (!applikes.includes(rkey)) {
missing.likes.push([rkey, record]);
}
}
for (let [rkey, record] of posts) {
if (!appposts.includes(rkey)) {
missing.posts.push([rkey, record]);
}
}
for (let [rkey, record] of reposts) {
if (!appreposts.includes(rkey)) {
missing.reposts.push([rkey, record]);
}
}
console.log(`There are ${missing.likes.length} missing likes, ${missing.posts.length} missing posts, ${missing.reposts.length} missing reposts`);
let missing_post_references = [...missing.likes, ...missing.reposts].filter(([rkey, record]) => record.subject.uri.includes("app.bsky.feed.post"));
let referenced_posts_that_still_exist = [];
for (let i = 0; i < missing_post_references.length; i += 25) {
let uris = missing_post_references.slice(i, i + 25).map(x => x[1].subject.uri);
let q = uris.map(u => `uris=${encodeURIComponent(u)}`).join('&');
let res = await fetch(`${pds}/xrpc/app.bsky.feed.getPosts?${q}`, {
headers: {"Authorization": `Bearer ${accessJwt}`}
});
let j = await res.json();
console.debug(j.posts);
referenced_posts_that_still_exist.push(...j.posts.map(p => p.uri));
}
missing.likes = missing.likes.filter(([rkey, record]) => referenced_posts_that_still_exist.includes(record.subject.uri));
missing.reposts = missing.reposts.filter(([rkey, record]) => referenced_posts_that_still_exist.includes(record.subject.uri));
console.log(`${missing.likes.length} missing liked posts still exist, ${missing.reposts.length} missing reposts still exist`);
fs.writeFileSync("missing.json", JSON.stringify(missing));
var writes = [];
for (let [records, collection] of [[missing.likes, "app.bsky.feed.like"], [missing.posts, "app.bsky.feed.post"], [missing.reposts, "app.bsky.feed.repost"]]) {
for (let [rkey, record] of records) {
writes.push({
collection,
rkey,
action: "delete",
$type: "com.atproto.repo.applyWrites#delete"
});
writes.push({
collection,
rkey,
action: "create",
$type: "com.atproto.repo.applyWrites#create",
value: record
});
}
}
let allresults = [];
for (let i = 0; i < writes.length; i += 100) {
let subwrites = writes.slice(i, i + 100);
let res = await fetch(`${pds}/xrpc/com.atproto.repo.applyWrites`, {
method: "POST",
headers: {"Authorization": `Bearer ${accessJwt}`, "Content-Type": "application/json"},
body: JSON.stringify({
repo: did,
writes: subwrites,
validate: true
})
});
console.log(res.status);
let {results} = await res.json();
allresults.push(...results);
}
fs.writeFileSync("allresults.json", JSON.stringify(allresults));
// wont show in appview idk how generate proper rkey but it's not important
/*var x = 0;
var writes = [];
for (let {$type, uri, cid} of allresults) {
if ($type == "com.atproto.repo.applyWrites#createResult" && uri.includes("app.bsky.feed.post")) {
writes.push({
collection: "app.bsky.feed.post",
rkey: `aaaaa${x++}`,
action: "create",
$type: "com.atproto.repo.applyWrites#create",
value: {
$type: "app.bsky.feed.post",
createdAt: new Date().toISOString(),
langs: ["en"],
embed: {
$type: "app.bsky.embed.record",
record: {
uri,
cid
}
},
text: "post missing from appview restored with script"
}
});
}
}
for (let i = 0; i < writes.length; i += 100) {
let subwrites = writes.slice(i, i + 100);
let res = await fetch(`${pds}/xrpc/com.atproto.repo.applyWrites`, {
method: "POST",
headers: {"Authorization": `Bearer ${accessJwt}`, "Content-Type": "application/json"},
body: JSON.stringify({
repo: did,
writes: subwrites,
validate: true
})
});
console.log(res.status, await res.text());
}*/
debugger;