Skip to content

Instantly share code, notes, and snippets.

@postspectacular
Last active September 17, 2025 10:24
Show Gist options
  • Save postspectacular/599fbf6ebe84ebaf7e2349a1ac8039e4 to your computer and use it in GitHub Desktop.
Save postspectacular/599fbf6ebe84ebaf7e2349a1ac8039e4 to your computer and use it in GitHub Desktop.
Configurable batch deletion of Mastodon bookmarks (using Mastodon API). MIT Licensed.
// your account domain
const DOMAIN = "mastodon.social";
// your registered mastodon app's access token
// (might need to first create an app via mastodon preferences and
// assign scopes: read:bookmarks and write:bookmarks)
const ACCESS_TOKEN = "TODO";
// only remove bookmarks older than this date
const THRESHOLD_DATE = "2025-06-01";
// set to null to start checking with newest bookmarks
const MAX_ID = null;
// don't delete bookmarks from those accounts
// (all accounts must be lowercase)
const KEEP_ACCOUNTS = [
"toxi", // your local account name
"[email protected]",
// ...
];
// don't delete bookmarks with these tags
const KEEP_TAGS = [
"art",
"opensource",
"silentsunday",
// ...
];
const HEADERS = { Authorization: `Bearer ${ACCESS_TOKEN}` };
let pagination = MAX_ID ? `max_id=${MAX_ID}` : "";
const sleep = (x) =>
new Promise((resolve) => {
console.log("sleep", x);
setTimeout(resolve, x * 1000);
});
const processPost = async (post) => {
if (post.created_at >= THRESHOLD_DATE) return;
if (KEEP_ACCOUNTS.includes(post.account.acct.toLowerCase())) return;
if (post.tags.some((tag) => KEEP_TAGS.includes(tag.name.toLowerCase())))
return;
console.log(
"removing bookmark:",
post.created_at,
post.account.acct,
"\n",
post.url,
"\n",
post.content
);
for (let i = 1; i <= 5; i++) {
try {
const res = await fetch(
`https://${DOMAIN}/api/v1/statuses/${post.id}/unbookmark`,
{ method: "POST", headers: HEADERS }
);
if (res.ok) return true;
console.log(res);
} catch (e) {
console.warn(e.message);
}
await sleep(i * 5);
}
};
let total = 0;
while (true) {
console.log("fetching bookmarks", pagination);
const resp = await fetch(
`https://${DOMAIN}/api/v1/bookmarks?limit=40&${pagination}`,
{
headers: HEADERS,
}
);
const posts = await resp.json();
console.log("loaded", posts.length, "bookmarks, ", posts[0].created_at);
let num = 0;
for (let post of posts) {
if (await processPost(post)) {
await sleep(1);
num++;
}
}
total += num;
console.log("removed bookmarks", num, "total", total);
const pageInfo = /max_id=\d+/.exec(resp.headers.get("link"));
if (!pageInfo) break;
pagination = pageInfo[0];
await sleep(num ? 10 : 2);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment