All checks were successful
Deploy Indiekit Server / deploy (push) Successful in 1m13s
When a post is deleted from the web backend (Micropub action=delete), call each registered syndicator's delete() method so the post is also removed from the Fediverse (AP Delete/Tombstone) and Bluesky (com.atproto.repo.deleteRecord). - patch-bluesky-syndicator-delete: adds Bluesky#deletePost(bskyUrl) to lib/bluesky.js and BlueskySyndicator#delete(url, syndication) to index.js; the bsky.app URL is resolved from the syndication array that postData.delete() preserves in _deletedProperties - patch-micropub-delete-propagation: patches action.js case "delete" to iterate publication.syndicationTargets after postContent.delete() and fire syndicator.delete() fire-and-forget for any syndicator that exposes the method (errors logged, never break the 200 response) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
166 lines
5.3 KiB
JavaScript
166 lines
5.3 KiB
JavaScript
/**
|
|
* Patch: add delete() support to the Bluesky syndicator.
|
|
*
|
|
* When a Micropub delete fires, Indiekit should be able to call
|
|
* syndicator.delete(url, syndication) to remove the corresponding post from
|
|
* Bluesky. This syndicator currently has no delete() method at all.
|
|
*
|
|
* Fix A (bluesky.js):
|
|
* Add Bluesky#deletePost(bskyUrl) — resolves the AT-URI from the bsky.app
|
|
* profile URL (using the existing getPostParts helper) and calls
|
|
* com.atproto.repo.deleteRecord to permanently delete the record.
|
|
*
|
|
* Fix B (index.js):
|
|
* Add BlueskySyndicator#delete(url, syndication) — finds the bsky.app URL
|
|
* in the syndication array passed by the Micropub delete action and delegates
|
|
* to Bluesky#deletePost().
|
|
*/
|
|
|
|
import { access, readFile, writeFile } from "node:fs/promises";
|
|
|
|
const MARKER = "// [patch] bluesky-syndicator-delete";
|
|
|
|
// ── Fix A: bluesky.js — add deletePost() method ───────────────────────────────
|
|
|
|
const blueskyClassCandidates = [
|
|
"node_modules/@rmdes/indiekit-syndicator-bluesky/lib/bluesky.js",
|
|
];
|
|
|
|
const OLD_POST_REPOST = ` async postRepost(postUrl) {
|
|
const client = await this.#client();
|
|
const post = await this.getPost(postUrl);
|
|
const repost = await client.repost(post.uri, post.cid);
|
|
return uriToPostUrl(this.profileUrl, repost.uri);
|
|
}
|
|
|
|
/**
|
|
* Post a quote post`;
|
|
|
|
const NEW_POST_REPOST = ` async postRepost(postUrl) {
|
|
const client = await this.#client();
|
|
const post = await this.getPost(postUrl);
|
|
const repost = await client.repost(post.uri, post.cid);
|
|
return uriToPostUrl(this.profileUrl, repost.uri);
|
|
}
|
|
|
|
/**
|
|
* Delete a Bluesky post by its bsky.app profile URL. ${MARKER}
|
|
* @param {string} bskyUrl - bsky.app post URL (e.g. https://bsky.app/profile/handle/post/rkey)
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async deletePost(bskyUrl) { ${MARKER}
|
|
const client = await this.#client();
|
|
const postParts = getPostParts(bskyUrl);
|
|
await client.com.atproto.repo.deleteRecord({
|
|
repo: postParts.did,
|
|
collection: "app.bsky.feed.post",
|
|
rkey: postParts.rkey,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Post a quote post`;
|
|
|
|
// ── Fix B: index.js — add delete() method to BlueskySyndicator ───────────────
|
|
|
|
const indexCandidates = [
|
|
"node_modules/@rmdes/indiekit-syndicator-bluesky/index.js",
|
|
];
|
|
|
|
const OLD_SYNDICATE_END = ` } catch (error) {
|
|
throw new IndiekitError(error.message, {
|
|
cause: error,
|
|
plugin: this.name,
|
|
status: error.statusCode,
|
|
});
|
|
}
|
|
}
|
|
|
|
init(Indiekit) {`;
|
|
|
|
const NEW_SYNDICATE_END = ` } catch (error) {
|
|
throw new IndiekitError(error.message, {
|
|
cause: error,
|
|
plugin: this.name,
|
|
status: error.statusCode,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete a previously syndicated post from Bluesky. ${MARKER}
|
|
* @param {string} url - Original blog post URL (unused, for API parity)
|
|
* @param {string[]} [syndication=[]] - Array of syndication URLs from the deleted post
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async delete(url, syndication = []) { ${MARKER}
|
|
try {
|
|
const bskyUrl = syndication.find((s) => s.includes("bsky.app"));
|
|
if (!bskyUrl) {
|
|
console.warn(\`[Bluesky] No Bluesky URL found in syndication for \${url} — skipping delete\`);
|
|
return;
|
|
}
|
|
const bluesky = new Bluesky({
|
|
identifier: this.options?.handle,
|
|
password: this.options?.password,
|
|
profileUrl: this.#profileUrl,
|
|
serviceUrl: this.#serviceUrl,
|
|
});
|
|
await bluesky.deletePost(bskyUrl);
|
|
console.log(\`[Bluesky] Deleted syndicated post: \${bskyUrl}\`);
|
|
} catch (error) {
|
|
throw new IndiekitError(error.message, {
|
|
cause: error,
|
|
plugin: this.name,
|
|
status: error.statusCode,
|
|
});
|
|
}
|
|
}
|
|
|
|
init(Indiekit) {`;
|
|
|
|
async function exists(p) {
|
|
try { await access(p); return true; } catch { return false; }
|
|
}
|
|
|
|
async function applyPatch(candidates, oldSnippet, newSnippet, label) {
|
|
let checked = 0;
|
|
let patched = 0;
|
|
|
|
for (const filePath of candidates) {
|
|
if (!(await exists(filePath))) continue;
|
|
checked++;
|
|
|
|
const source = await readFile(filePath, "utf8");
|
|
if (source.includes(MARKER)) {
|
|
console.log(`[postinstall] patch-bluesky-syndicator-delete: ${label} already applied to ${filePath}`);
|
|
continue;
|
|
}
|
|
|
|
if (!source.includes(oldSnippet)) {
|
|
console.warn(`[postinstall] patch-bluesky-syndicator-delete: ${label} snippet not found in ${filePath}`);
|
|
continue;
|
|
}
|
|
|
|
await writeFile(filePath, source.replace(oldSnippet, newSnippet), "utf8");
|
|
patched++;
|
|
console.log(`[postinstall] Applied patch-bluesky-syndicator-delete (${label}) to ${filePath}`);
|
|
}
|
|
|
|
return { checked, patched };
|
|
}
|
|
|
|
const a = await applyPatch(blueskyClassCandidates, OLD_POST_REPOST, NEW_POST_REPOST, "Bluesky#deletePost");
|
|
const b = await applyPatch(indexCandidates, OLD_SYNDICATE_END, NEW_SYNDICATE_END, "BlueskySyndicator#delete");
|
|
|
|
const totalChecked = a.checked + b.checked;
|
|
const totalPatched = a.patched + b.patched;
|
|
|
|
if (totalChecked === 0) {
|
|
console.log("[postinstall] patch-bluesky-syndicator-delete: no target files found");
|
|
} else if (totalPatched === 0) {
|
|
console.log("[postinstall] patch-bluesky-syndicator-delete: already up to date");
|
|
} else {
|
|
console.log(`[postinstall] patch-bluesky-syndicator-delete: patched ${totalPatched} file(s)`);
|
|
}
|