Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stefanbohacek/4dde5b0231e504a4ae29e234306b8602 to your computer and use it in GitHub Desktop.
Save stefanbohacek/4dde5b0231e504a4ae29e234306b8602 to your computer and use it in GitHub Desktop.
Source code for the "Preview domain blocks before moving to a new Mastodon instance" blog post.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65"
crossorigin="anonymous"
/>
</head>
<body>
<div class="bg-light p-5 mt-5 mb-5">
<form id="data-form">
<div class="mb-3">
<label for="instance" class="form-label">Mastodon instance</label>
<div class="input-group">
<span class="input-group-text" id="basic-addon3">https://</span>
<input
required
type="text"
class="form-control"
name="instance"
id="instance"
placeholder="mastodon.social"
/>
</div>
<div class="form-text">
The name of the Mastodon instance, without <code>https</code>.
</div>
</div>
<div class="mb-3">
<label for="data" class="form-label"
>Fediverse connections data</label
>
<input
required
class="form-control"
type="file"
name="data"
id="data"
accept=".csv"
/>
<div class="form-text">
Download yours
<a
href="https://data.stefanbohacek.dev/projects/fediverse"
target="_blank"
>here</a
>.
</div>
</div>
<button id="form-submit" type="submit" class="btn btn-primary">
Submit
</button>
</form>
<div id="results"></div>
</div>
<script>
const csvToJSON = (csv, headers, quoteChar = '"', delimiter = ",") => {
const regex = new RegExp(
`\\s*(${quoteChar})?(.*?)\\1\\s*(?:${delimiter}|$)`,
"gs"
);
const match = (line) =>
[...line.matchAll(regex)].map((m) => m[2]).slice(0, -1);
const lines = csv.split("\n");
const heads = headers ?? match(lines.shift());
return lines.map((line) => {
return match(line).reduce((acc, cur, i) => {
const val = cur.length <= 0 ? null : Number(cur) || cur;
const key = heads[i] ?? `extra_${i}`;
return { ...acc, [key]: val };
}, {});
});
};
const getDomainBlocks = async (instance) => {
let results = {};
try {
const resp = await fetch(
`https://${instance}/api/v1/instance/domain_blocks`
);
const respJSON = await resp.json();
respJSON.forEach((domain) => {
if (domain.severity === "suspend") {
results[domain.domain] = domain;
}
});
} catch (error) {
alert(`Domain block list not available for ${instance}.`);
toggleFormElements(form, false);
} finally {
return results;
}
};
const toggleFormElements = (form, disabled) => {
const formElements = form.elements;
for (element of formElements) {
if (element.tagName === "BUTTON") {
element.disabled = disabled;
} else {
element.readOnly = disabled;
}
}
};
const form = document.getElementById("data-form");
const formSubmitBtn = document.getElementById("form-submit");
const resultsElement = document.getElementById("results");
form.addEventListener("submit", (event) => {
toggleFormElements(form, true);
event.preventDefault();
const formData = new FormData(form);
const values = [...formData.entries()];
const instance = formData.get("instance");
const data = formData.get("data");
let lostContacts = [];
let reader = new FileReader();
reader.onload = async (e) => {
const dataJSON = csvToJSON(e.target.result);
const blockedDomains = await getDomainBlocks(instance);
let resultsHTML = "";
if (blockedDomains) {
const blockedDomainNames = Object.keys(blockedDomains);
if (blockedDomainNames && blockedDomainNames.length) {
for (const instance of dataJSON) {
if (blockedDomainNames.includes(instance.domain)) {
instance.comment = blockedDomains[instance.domain].comment;
lostContacts.push(instance);
}
}
if (lostContacts && lostContacts.length) {
resultsHTML = `
<p class="mt-4">By moving to ${instance}, you will lose ${lostContacts.length.toLocaleString()} connection(s).</p>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Instance</th>
<th scope="col">Connections</th>
<th scope="col">Comment</th>
</tr>
</thead>
<tbody>
${lostContacts
.map(
(domain) => `
<tr>
<th scope="row">${domain.domain}</th>
<td>${domain.connections.toLocaleString()}</td>
<td>${domain.comment}</td>
</tr>
`
)
.join("")}
</tbody>
</table>
`;
} else {
resultsHTML = `<p class="mt-4"><strong>Good news!</strong> None of your contacts' instances are blocked by ${instance}.`;
}
}
}
resultsElement.innerHTML = resultsHTML;
if (lostContacts && lostContacts.length){
resultsElement.scrollIntoView({
behavior: 'smooth'
});
}
toggleFormElements(form, false);
};
reader.readAsBinaryString(data);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment