Last active
February 21, 2023 20:23
-
-
Save KevinPayravi/39ce33ac0f253034e101ba6f6874865d to your computer and use it in GitHub Desktop.
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
// USE AT YOUR OWN RISK | |
// This is a JavaScript script edits your statuses/toots | |
// It's pretty rudimentary and doesn't account for all possible cases | |
// | |
// This script will iterate through a list of Mastodon statuses, | |
// searching for Twitter t.co shortlinks and replacing them with their actual targets. | |
// When a t.co link is found, this script makes a web request to find its target and updates it. | |
// It will also convert all http:// links to https:// | |
// | |
// While running, this script will output progress to the console so you can check for erros | |
// Any time a status is updated, it will open the status in a new tab so you can verify | |
// Populate these values: | |
const statusIds = ["12345678912345678"]; // Array of status IDs extracted from Mastodon archive | |
const BASE_URL = 'https://example.com'; // URL for your Mastodon server | |
const USERNAME = '@example'; // Your Mastdon username with the '@' | |
const TOKEN = ''; // Mastodon API token | |
const TCO_REGEX = /t\.co\/[a-zA-Z0-9\-\.]*/g; | |
const parser = new DOMParser(); | |
const req = new XMLHttpRequest(); | |
// Strip HTML for a string | |
// Mastodon returns statuses as HTML, which needs to be stripped | |
// because Mastodon's PUT endpoint takes a plaintext string | |
function strip(html) { | |
let doc = new DOMParser().parseFromString(html, 'text/html'); | |
return doc.body.textContent || ""; | |
} | |
// Make a web request to find a t.co link's target | |
async function resolveTcoUrl(url) { | |
return new Promise(function (resolve) { | |
url = url.replace('?amp=1', ''); | |
req.open("GET", url, true); | |
req.send(); | |
req.onreadystatechange = function () { | |
if (this.readyState == 4) { | |
var responseHTML = parser.parseFromString(req.responseText, 'text/html'); | |
resolve(responseHTML.getElementsByTagName('title')[0].innerHTML); | |
} | |
}; | |
}); | |
}; | |
// Make a GET request | |
async function get(url) { | |
const response = await fetch(url, { | |
method: 'GET', | |
headers: { | |
'Content-type': 'application/json', | |
'Authorization': 'Bearer ' + TOKEN | |
} | |
}); | |
return response; | |
}; | |
// Make a PUT request | |
async function put(url, data) { | |
const response = await fetch(url, { | |
method: 'PUT', | |
headers: { | |
'Content-type': 'application/json', | |
'Authorization': 'Bearer ' + TOKEN | |
}, | |
body: JSON.stringify(data) | |
}); | |
return response; | |
}; | |
// Sleep function | |
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); | |
async function main() { | |
(async () => { | |
for (let i = 0; i < statusIds.length; i++) { | |
// Get status: | |
var status = await get(BASE_URL + '/api/v1/statuses/' + statusIds[i]); | |
var statusResponse = await status.json(); | |
console.log('Got Toot ' + statusIds[i]); | |
// Sleep to avoid rate limiting: | |
await sleep(5000); | |
// If status includes a t.co link, start the replacement | |
if (statusResponse.content.includes('t.co/')) { | |
var newContent = strip(statusResponse.content); | |
console.log('Original status: ' + newContent) | |
var results = [...statusResponse.content.matchAll(TCO_REGEX)]; | |
for (let j = 0; j < results.length; j++) { | |
var newUrl = await resolveTcoUrl('https://' + results[j]); | |
// Replace t.co link with destination link | |
newContent = newContent.replaceAll(results[j], newUrl.replace(/^https?:\/\//, '')); | |
// Replace http:// with https:// | |
newContent = newContent.replaceAll('http://', 'https://'); | |
} | |
console.log('====================='); | |
console.log('Edited status: ' + newContent); | |
console.log(statusResponse); | |
// Attach existing media for the PUT | |
// Making a PUT without attaching any existing media will remove that media from the status | |
if (statusResponse.media_attachments.length > 0) { | |
var update = await put(BASE_URL + '/api/v1/statuses/' + statusIds[i], { 'status': newContent, 'media_ids': statusResponse.media_attachments.map(function (el) { return el.id; }) }); | |
} else { | |
var update = await put(BASE_URL + '/api/v1/statuses/' + statusIds[i], { 'status': newContent }); | |
} | |
await sleep(3000); | |
var updateResponse = await update.json(); | |
console.log(updateResponse); | |
// Open updated status in new tab, so you can double-check for any errors: | |
window.open(BASE_URL + '/' + USERNAME + '/' + statusIds[i], '_blank'); | |
} | |
} | |
})(); | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment