Last active
September 17, 2025 10:24
-
-
Save postspectacular/599fbf6ebe84ebaf7e2349a1ac8039e4 to your computer and use it in GitHub Desktop.
Configurable batch deletion of Mastodon bookmarks (using Mastodon API). MIT Licensed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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