Last active
June 27, 2024 11:11
-
-
Save gnyman/35dd1c3cd0c7105aa992a5a2e240c5c2 to your computer and use it in GitHub Desktop.
Scriptable.App Mastodon Thread Saver
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
// 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