Last active
April 23, 2023 23:45
-
-
Save rickosborne/22c70f6f88f2b6c516813a36d3e75e16 to your computer and use it in GitHub Desktop.
Bookmarklet to extract a Mastodon thread into JSON
This file contains 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
javascript:(function () { | |
/* ======================================== | |
Mastodon thread scraper, by Rick Osborne. | |
Copy this entire file, from the opening "javascript:" to the last "();". | |
Create a new bookmark in your browser, using the copied text as the URL. | |
When you are on a Mastodon thread page, activate this bookmark. | |
======================================== */ | |
function findOne(parent, selector, block) { | |
if (parent == null) { | |
return undefined; | |
} | |
var els = parent.querySelectorAll(selector); | |
if (els.length === 1) { | |
if (block != null) { | |
block(els[0]); | |
} | |
return els[0]; | |
} | |
console.log(`Not found: ${selector}`, els); | |
return undefined; | |
} | |
function numberish(s) { | |
return ["number", "string"].includes(typeof s) ? Number(s) : undefined; | |
} | |
function ancestor(el, className) { | |
if (el == null || el.classList.contains(className)) { | |
return el; | |
} | |
return ancestor(el.parentElement, className); | |
} | |
function author(el) { | |
var a = {}; | |
findOne(el, ".display-name .display-name__html", e => a.name = e.innerText); | |
findOne(el, ".display-name .display-name__account", e => a.handle = e.innerText); | |
findOne(el, ".detailed-status__display-name, .status__display-name", header => { | |
author.href = header.href; | |
var avatar = findOne(header, ".account__avatar img"); | |
author.avatar = avatar?.src; | |
author.id = avatar?.alt; | |
}); | |
return a; | |
} | |
function elChildren(el, tagName, block) { | |
const children = [...el.children].filter(e => e.tagName); | |
if (block != null) { | |
block(children); | |
} | |
return children; | |
} | |
function elNum(el, tagName, index, block) { | |
const child = elChildren(el, tagName).at(index); | |
if (block != null && child != null) { | |
block(child); | |
} | |
return child; | |
} | |
var top = document.querySelector(".detailed-status"); | |
if (top == null) { | |
alert("No detailed status found. Use this bookmarklet on a detailed status page."); | |
return; | |
} | |
var post = { | |
author: author(top) | |
}; | |
findOne(top, ".status__content .status__content__text", e => post.html = e.innerHTML); | |
findOne(top, ".detailed-status__meta", (meta) => { | |
findOne(meta, ".detailed-status__datetime", dt => { | |
post.url = dt.href; | |
findOne(dt, "span", e => post.timestamp = e.innerText); | |
}); | |
findOne(meta, ".dropdown-menu__text-button > span", e => post.edited = e.innerText); | |
findOne(meta, "i:first-of-type", e => post.visibility = e.title); | |
findOne(meta, ".detailed-status__reblogs span span span", e => post.boosts = numberish(e.innerText)); | |
findOne(meta, ".detailed-status__link span span span", e => post.favorites = numberish(e.innerText)); | |
}); | |
var scrollable = ancestor(top, "scrollable"); | |
elNum(scrollable, "DIV", 1, replyContainer => { | |
var replies = elChildren(replyContainer, "DIV"); | |
post.replies = replies.map(replyEl => { | |
var reply = {}; | |
findOne(replyEl, ".status", statusEl => { | |
reply.id = statusEl.getAttribute("data-id"); | |
findOne(statusEl, ".status__relative-time", e => reply.href = e.href); | |
findOne(statusEl, ".status__visibility-icon i", e => reply.visibility = e.title); | |
findOne(statusEl, "time", e => reply.timestamp = e.getAttribute("datetime")); | |
reply.author = author(statusEl); | |
findOne(statusEl, ".status__content .status__content__text", e => reply.html = e.innerHTML); | |
}); | |
return reply; | |
}); | |
}); | |
console.log(post); | |
if (navigator.clipboard) { | |
navigator.clipboard.writeText(JSON.stringify(post, null, 2)).then(() => { | |
alert("The thread has been copied to your clipboard in JSON format.") | |
}); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment