Skip to content

Instantly share code, notes, and snippets.

@gnyman
Last active June 27, 2024 11:11
Show Gist options
  • Save gnyman/35dd1c3cd0c7105aa992a5a2e240c5c2 to your computer and use it in GitHub Desktop.
Save gnyman/35dd1c3cd0c7105aa992a5a2e240c5c2 to your computer and use it in GitHub Desktop.
Scriptable.App Mastodon Thread Saver
// This is a simple https://scriptable.app script which receives link to a mastodon status
// and fetches the thread and converts it to markdown (as this was designed for glitch instances
// which supports html/markdown) and then returns it.
// No warranty or promise that this will work. It's a "bodge".
const DEFAULT_URL = "https://infosec.exchange/@gnyman/112688072805600720";
// Function to fetch JSON data from a URL
async function fetchJSON(url) {
const request = new Request(url);
try {
const response = await request.loadJSON();
console.log("Fetch successful: " + url);
console.log(JSON.stringify(response));
return response;
} catch (error) {
console.error("Error fetching URL: " + url + " - " + error);
throw error;
}
}
// Function to convert HTML to Markdown
function htmlToMarkdown(html) {
// Replace line breaks with double line breaks
html = html.replace(/<br\s*\/?>/gi, "\n\n");
// Convert blockquotes
html = html.replace(/<blockquote>([\s\S]*?)<\/blockquote>/gi, function(match, p1) {
return "\n\n> " + p1.replace(/\n/g, "\n> ") + "\n\n";
});
// Convert ordered lists
html = html.replace(/<ol>([\s\S]*?)<\/ol>/gi, function(match, p1) {
let count = 1;
return p1.replace(/<li>([\s\S]*?)<\/li>/gi, function(match, p2) {
return "\n" + (count++) + ". " + p2 + "\n";
});
});
// Convert unordered lists
html = html.replace(/<ul>([\s\S]*?)<\/ul>/gi, function(match, p1) {
return p1.replace(/<li>([\s\S]*?)<\/li>/gi, function(match, p2) {
return "\n- " + p2 + "\n";
});
});
// Convert code blocks
html = html.replace(/<pre><code>([\s\S]*?)<\/code><\/pre>/gi, function(match, p1) {
return "\n```\n" + p1 + "\n```\n";
});
// Convert inline code
html = html.replace(/<code>([\s\S]*?)<\/code>/gi, function(match, p1) {
return "`" + p1 + "`";
});
// Convert bold text
html = html.replace(/<strong>([\s\S]*?)<\/strong>/gi, function(match, p1) {
return "**" + p1 + "**";
});
// Convert italic text
html = html.replace(/<em>([\s\S]*?)<\/em>/gi, function(match, p1) {
return "*" + p1 + "*";
});
// Convert underline text
html = html.replace(/<u>([\s\S]*?)<\/u>/gi, function(match, p1) {
return "__" + p1 + "__";
});
// Convert strikethrough text
html = html.replace(/<del>([\s\S]*?)<\/del>/gi, function(match, p1) {
return "~~" + p1 + "~~";
});
// Remove remaining HTML tags
html = html.replace(/<[^>]*>/g, "");
return html;
}
// Function to fetch the Mastodon thread
async function fetchMastodonThread(url) {
const statusId = url.split("/").pop();
const instanceUrl = url.split("/@")[0];
const contextUrl = `${instanceUrl}/api/v1/statuses/${statusId}/context`;
const statusUrl = `${instanceUrl}/api/v1/statuses/${statusId}`;
// Fetch the main toot
const statusResponse = await fetchJSON(statusUrl);
const accountId = statusResponse.account.id;
// Fetch ancestors and descendants
const contextResponse = await fetchJSON(contextUrl);
const ancestors = contextResponse.ancestors.filter(status => status.account.id === accountId) || [];
const descendants = contextResponse.descendants.filter(status => status.account.id === accountId) || [];
const thread = [...ancestors, statusResponse, ...descendants];
return thread.map(status => ({
content: htmlToMarkdown(status.content),
author: status.account.display_name,
created_at: status.created_at
}));
}
// Function to convert thread to Markdown
function convertToMarkdown(thread) {
return thread.map(status => {
const { content, author, created_at } = status;
return `### ${author}\n*${created_at}*\n\n${content}\n`;
}).join("\n---\n");
}
// Main function
async function main() {
let mastodonUrl = args.shortcutParameter;
if (!mastodonUrl) {
mastodonUrl = DEFAULT_URL;
console.log("No URL provided. Using default URL: " + mastodonUrl);
}
try {
const thread = await fetchMastodonThread(mastodonUrl);
const markdown = convertToMarkdown(thread);
const result = markdown + `\n\n[Link to original toot](${mastodonUrl})`;
console.log(result);
return result;
} catch (error) {
console.error("Error fetching thread: " + error);
}
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment