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;