--- /dev/null
+<html>
+ <head>
+ <title>MastoWoT?</title>
+ <style>
+ body {
+ background-color: black;
+ padding: 0px;
+ margin: 0px;
+ color: #CAA;
+ font-family: "Audiowide", "Sans";
+ }
+
+ h1 {
+ width: 100%;
+ text-style: italic;
+ text-align: center;
+ }
+
+ h2 {
+ display: inline-block;
+ margin: .3em .5em .5em .5em;
+ vertical-align: middle;
+ }
+
+ .panelbutton {
+ display: inline-block;
+ }
+
+ #settingsPanel #sourceHost {
+ display: inline-block;
+ width: 100%;
+ }
+
+ #hostsPanel {
+ display: inline-block;
+ width: calc( 70% - 10px );
+ min-width: 30em;
+ height: calc(100% - 3em);
+ /*background-color: #600;
+ border: 3px solid #A00;*/
+ margin: 0px;
+ vertical-align: top;
+ }
+
+/* #hostsTrusted, #hostsUntrusted, #hostsKnown, #manPanel {*/
+ .subpanel, #manPanel {
+ padding: 0em 1em;
+ border: 3px solid #C00;
+ border-radius: 1em;
+ background-color: #600;
+ margin-bottom: 1em;
+ }
+
+ #settingsPanel {
+ display: inline-block;
+ width: calc( 30% - 10px - 2em);
+ min-width: 10em;
+ height: calc(100% - 2em);
+ background-color: #400;
+ border: 3px solid #C00;
+ margin: 0px;
+ border-radius: 1em;
+ padding: 1em;
+ vertical-align: top;
+ }
+
+ #settingsPanel a {
+ display: block;
+ width: 100%;
+ text-align: center;
+ margin-bottom: 1em;
+ font-weight: bold;
+ color: #C00;
+ }
+
+ #settingsPanel button {
+ display: block;
+ width: 100%;
+ }
+
+ #settingsPanel div {
+ border: 1px solid black;
+ border-radius: 1em;
+ padding: 0em 0em 1em 0em;
+ margin-top: 1em;
+ }
+
+ #settingsPanel h2 {
+ width: 100%;
+ }
+
+ #settingsPanel label {
+ margin-left: 10%;
+ display: inline-block;
+ width: 40%;
+ }
+
+ #settingsPanel input {
+ display: inline-block;
+ width: 40%;
+ }
+
+ #shade {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ background-color: black;
+ opacity: 0.8;
+ z-index: 100;
+ left: 0px;
+ top: 0px;
+ }
+
+ #shade-text {
+ position: fixed;
+ width: 40%;
+ height: 40%;
+ left: 30%;
+ top: 30%;
+ z-index: 150;
+ }
+
+ #shade-text-button {
+ position: fixed;
+ width: 40%;
+ height: 5%;
+ left: 30%;
+ top: 70%;
+ z-index: 150;
+ font-size: 3vh;
+ }
+
+ .subpanel .list {
+ display: none;
+ }
+
+ .subpanel .list .instanceElem:nth-child(2n+3) {
+ background-color: #C11;
+ }
+
+ .subpanel .list div {
+ display: block;
+ position: relative;
+ border-bottom: 1px solid black;
+ }
+
+ .subpanel .list div h3 {
+ display: inline-block;
+ width: calc( 50% - 20em );
+ margin: 0em;
+ padding: 0em;
+ overflow: scroll;
+ }
+
+ .subpanel .list div div {
+ display: inline-block;
+ position: relative;
+ width: 20em;
+ vertical-align: middle;
+ }
+
+ .subpanel .list div .comments {
+ display: inline-block;
+ margin: none;
+ width: 48%;
+ border: none;
+ }
+
+ .subpanel .list div div input[type=text] {
+ display: inline-block;
+ width: 3em;
+ vertical-align: middle;
+ }
+
+ .subpanel .list div div input[type=range] {
+ display: inline-block;
+ width: 17em;
+ vertical-align: middle;
+ }
+
+ .subpanel .list div div h3 {
+ width: 100%;
+ }
+
+ .subpanel .list div:first-child h3 {
+ font-weight: bold;
+ text-decoration: underline;
+ }
+ </style>
+ <script>
+ // Initialize the site by loading in a list of known hosts
+ var instances = [];
+ var subLists = [];
+
+ function init() {
+ var buttons = document.getElementsByClassName("panelButton");
+ for(var i = 0; i < buttons.length; i++) {
+ buttons[i].onclick = openPanel;
+ }
+ }
+
+ function synchronize() {
+ var sourceInstance = "https://" + document.getElementById("sourceHost").value; //mastodon.slightlycyberpunk.com";
+ fetch(sourceInstance + "/api/v1/instance/peers")
+ .then(response => response.json())
+ .then(peers => {
+ console.log(peers);
+// var instances = [];
+ for(var i = 0; i < peers.length; i++) {
+// var instance = directory[i].acct.replace( directory[i].username + "@", "" );
+ var instance = peers[i];
+
+ if( instances[instance] == null ) {
+ instances[instance] = new Object();
+ instances[instance].domain = instance;
+ instances[instance].reasons = [];
+ }
+ }
+// console.log(instances);
+ resetHosts();
+ });
+
+ fetch(sourceInstance + "/api/v1/instance/domain_blocks")
+ .then(response => response.json())
+ .then(blocklist => {
+ console.log(blocklist);
+
+ for(var i = 0; i < blocklist.length; i++) {
+ instance = blocklist[i].domain;
+ if(instance.indexOf("*") != -1) {
+ continue;
+ }
+ if(instances[instance] == null) {
+ instances[instance] = new Object();
+ instances[instance].domain = instance;
+ instances[instance].reasons = [];
+ }
+
+ if( blocklist[i].comment == null ) {
+ blocklist[i].comment = "[ No comments provided ]"
+ }
+ if( blocklist[i].severity == "silence" ) {
+ instances[instance].trust = -1 * document.getElementById("threshold-suspend").value;
+ instances[inst].reasons[instance] = "SILENCED: "+blocklist[i].comment;
+ } else {
+ instances[instance].trust = -1 * document.getElementById("threshold-limit");
+ instances[inst].reasons[instance] = "LIMITED: "+blocklist[i].comment;
+ }
+// instances
+ }
+ });
+
+// document.getElementById("processButton").onclick = process;
+ }
+
+ async function process() {
+ document.getElementById("processButton").disabled = true;
+ var total = 0;
+ for(instance in instances) {
+ if(instances[instance].trust > 0) {
+ try {
+ await fetch("https://"+instance+"/api/v1/instance/domain_blocks")
+ .then(response => response.json())
+ .then(blocklist => {
+ for(var i = 0; i < blocklist.length; i++) {
+ inst = blocklist[i].domain;
+ if(inst.indexOf("*") != -1) {
+ continue;
+ }
+ if(instances[inst] == null) {
+ instances[inst] = new Object();
+ instances[inst].domain = inst;
+ instances[inst].reasons = [];
+ }
+
+ if(typeof instances[inst].subtrust == "undefined") {
+ instances[inst].subtrust = 0;
+ }
+ if( blocklist[i].comment == null ) {
+ blocklist[i].comment = "[ No comments provided ]"
+ }
+ if( blocklist[i].severity == "silence" ) {
+ instances[inst].subtrust -= instances[instance].trust *
+ parseFloat(document.getElementById("multiplier-suspend").value);
+ instances[inst].reasons[instance] = "SILENCED: "+blocklist[i].comment;
+ } else {
+ instances[inst].subtrust -= instances[instance].trust *
+ parseFloat(document.getElementById("multiplier-limit").value);
+ instances[inst].reasons[instance] = "LIMITED: "+blocklist[i].comment;
+ }
+ }
+ total++;
+ });
+ } catch(e) {
+ console.log(e);
+ }
+ }
+ }
+ // Normalize trust values
+ for(instance in instances) {
+ if(typeof instances[instance].subtrust != "undefined") {
+ instances[instance].trust = instances[instance].subtrust / total;
+ if(instances[instance].trust > 100) { instances[instance].trust = 100; }
+ if(instances[instance].trust < -100) { instances[instance].trust = -100; }
+ }
+ }
+ resetHosts();
+ document.getElementById("processButton").disabled = false;
+ }
+
+ async function assocWithEnemies() {
+ document.getElementById("assocWithEnemies").disabled = true;
+ var total = 0;
+ subLists.aoe = [];
+ for(instance in instances) {
+ if(instances[instance].trust < 0) {
+ try {
+ var c = 0;
+ while(c < 10) {
+ await fetch("https://"+instance+"/api/v1/directory?limit=80&offset=0", { signal: AbortSignal.timeout(5000) })
+ .then(response => response.json())
+ .then(directory => {
+ if(directory.length < 80) {
+ c = 10;
+ }
+ for(var i = 0; i < directory.length; i++) {
+ inst = directory[i].acct.split("@")[1];
+ if(inst == null || inst.indexOf("*") != -1) {
+ continue;
+ }
+ if(subLists.aoe[inst] == null) {
+ subLists.aoe[inst] = new Object();
+ subLists.aoe[inst].domain = inst;
+ subLists.aoe[inst].reasons = [];
+ subLists.aoe[inst].trust = 0;
+ subLists.aoe[inst].aoesubtrust = 0;
+ }
+
+ subLists.aoe[inst].aoesubtrust += parseFloat(instances[instance].trust);
+ if(subLists.aoe[inst].aoesubtrust < total) {
+ total = subLists.aoe[inst].aoesubtrust;
+ }
+ }
+ });
+ c++;
+ }
+ } catch(e) {
+ console.log(e);
+ }
+ }
+ }
+ // Normalize trust values
+ for(instance in subLists.aoe) {
+ if(typeof subLists.aoe[instance].aoesubtrust != "undefined") {
+ subLists.aoe[instance].trust = (subLists.aoe[instance].aoesubtrust * 100) / total;
+ if(subLists.aoe[instance].trust > 100) { subLists.aoe[instance].trust = 100; }
+ if(subLists.aoe[instance].trust < -100) { subLists.aoe[instance].trust = -100; }
+ }
+ }
+ addTable("aoe", "Associates of Enemies", subLists.aoe);
+ document.getElementById("assocWithEnemies").disabled = false;
+ }
+
+ async function friendsOfFriends() {
+ document.getElementById("friendsOfFriends").disabled = true;
+ var total = 0;
+ subLists.fof = [];
+ for(instance in instances) {
+ if(instances[instance].trust > 0) {
+ try {
+ var c = 0;
+ while(c < 10) {
+ await fetch("https://"+instance+"/api/v1/directory?limit=80&offset="+(80*c), { signal: AbortSignal.timeout(5000) })
+ .then(response => response.json())
+ .then(directory => {
+ if(directory.length < 80) {
+ c = 10;
+ }
+ for(var i = 0; i < directory.length; i++) {
+ inst = directory[i].acct.split("@")[1];
+ if(inst == null || inst.indexOf("*") != -1) {
+ continue;
+ }
+ if(subLists.fof[inst] == null) {
+ subLists.fof[inst] = new Object();
+ subLists.fof[inst].domain = inst;
+ subLists.fof[inst].reasons = [];
+ subLists.fof[inst].trust = 0;
+ subLists.fof[inst].fofsubtrust = 0;
+ }
+
+ subLists.fof[inst].fofsubtrust += parseFloat(instances[instance].trust);
+ if(subLists.fof[inst].fofsubtrust > total) {
+ total = subLists.fof[inst].fofsubtrust;
+ }
+ }
+ });
+ c++;
+ }
+ } catch(e) {
+ console.log(e);
+ }
+ }
+ }
+ // Normalize trust values
+ for(instance in subLists.fof) {
+ if(typeof subLists.fof[instance].fofsubtrust != "undefined") {
+ subLists.fof[instance].trust = (int)(subLists.fof[instance].fofsubtrust * 100) / total;
+ if(subLists.fof[instance].trust > 100) { subLists.fof[instance].trust = 100; }
+ if(subLists.fof[instance].trust < -100) { subLists.fof[instance].trust = -100; }
+ }
+ }
+ addTable("fof", "Friends of Friends", subLists.fof);
+ document.getElementById("friendsOfFriends").disabled = false;
+ }
+
+
+ async function enemiesOfEnemies() {
+ document.getElementById("enemiesOfEnemies").disabled = true;
+ var total = 0;
+ subLists.eoe = [];
+ for(instance in instances) {
+ if(instances[instance].trust < 0) {
+ try {
+ await fetch("https://"+instance+"/api/v1/instance/domain_blocks", { signal: AbortSignal.timeout(5000) })
+ .then(response => response.json())
+ .then(blocklist => {
+ for(var i = 0; i < blocklist.length; i++) {
+ inst = blocklist[i].domain;
+ if(inst.indexOf("*") != -1) {
+ continue;
+ }
+ if(subLists.eoe[inst] == null) {
+ subLists.eoe[inst] = new Object();
+ subLists.eoe[inst].domain = inst;
+ subLists.eoe[inst].reasons = [];
+ subLists.eoe[inst].eoesubtrust = 0;
+ }
+
+ if( blocklist[i].comment == null ) {
+ blocklist[i].comment = "[ No comments provided ]"
+ }
+ if( blocklist[i].severity == "silence" ) {
+ subLists.eoe[inst].eoesubtrust -= instances[instance].trust *
+ parseFloat(document.getElementById("multiplier-suspend").value);
+ subLists.eoe[inst].reasons[instance] = "SILENCED: "+blocklist[i].comment;
+ } else {
+ subLists.eoe[inst].eoesubtrust -= instances[instance].trust *
+ parseFloat(document.getElementById("multiplier-limit").value);
+ subLists.eoe[inst].reasons[instance] = "LIMITED: "+blocklist[i].comment;
+ }
+ }
+ total++;
+ });
+ } catch(e) {
+ console.log(e);
+ }
+ }
+ }
+ // Normalize trust values
+ for(instance in subLists.eoe) {
+ if(typeof subLists.eoe[instance].eoesubtrust != "undefined") {
+ subLists.eoe[instance].trust = subLists.eoe[instance].eoesubtrust / total;
+ if(subLists.eoe[instance].trust > 100) { subLists.eoe[instance].trust = 100; }
+ if(subLists.eoe[instance].trust < -100) { subLists.eoe[instance].trust = -100; }
+ }
+ }
+ addTable("eoe", "Enemies of Enemies", subLists.eoe);
+ document.getElementById("enemiesOfEnemies").disabled = false;
+ }
+
+ function addTable(id, title, data) {
+ var mainDiv = document.getElementById("hosts"+id);
+ if(mainDiv != null) {
+ mainDiv.parentElement.removeChild(mainDiv);
+ }
+ mainDiv = document.createElement("div");
+ mainDiv.setAttribute("id", "hosts"+id);
+ mainDiv.setAttribute("class", "subpanel");
+
+ var button = document.createElement("button");
+ button.setAttribute("id", "hosts"+id+"Open");
+ button.setAttribute("class", "panelButton");
+ button.innerHTML = "+";
+ button.onclick = openPanel;
+ mainDiv.appendChild(button);
+
+ var header = document.createElement("h2");
+ header.innerHTML = title;
+ mainDiv.appendChild(header);
+
+ var listElem = document.createElement("div");
+ listElem.setAttribute("id", "hosts"+id+"List");
+ listElem.setAttribute("class", "list");
+ listElem.setAttribute("listId", id);
+ for(element in data) {
+ var instance = element;
+
+ var instElem = document.createElement("div");
+ instElem.innerHTML = "<h3>" + instance + "</h3>";
+ instElem.setAttribute("id", "instance-"+instance);
+ instElem.setAttribute("class", "instanceElem");
+
+ var commentsElem = document.createElement("div");
+ commentsElem.setAttribute("class", "comments");
+ commentsElem.innerHTML = "";
+ for(inst in data[element].reasons) {
+ commentsElem.innerHTML += inst + " - " + data[element].reasons[inst] + "<br>";
+ }
+ instElem.appendChild(commentsElem);
+
+ var divElem = document.createElement("div");
+ var rangeElem = document.createElement("input");
+ rangeElem.setAttribute("type", "range");
+ rangeElem.setAttribute("min", "-100");
+ rangeElem.setAttribute("max", "100");
+ rangeElem.setAttribute("value",data[element].trust);
+ rangeElem.oninput = syncRange;
+ rangeElem.onchange = updateRange;
+ divElem.appendChild(rangeElem);
+
+ var valElem = document.createElement("input");
+ valElem.setAttribute("type", "text");
+ valElem.value = data[element].trust;
+ valElem.onchange = updateVal;
+ divElem.appendChild(valElem);
+ instElem.appendChild(divElem);
+
+ addButton = document.createElement("button");
+ addButton.onclick = subListAdd;
+ addButton.innerHTML = "Add";
+ instElem.appendChild(addButton);
+
+ listElem.appendChild(instElem);
+ }
+ mainDiv.appendChild(listElem);
+
+ document.getElementById("hostsPanel").appendChild(mainDiv);
+ }
+
+ function subListAdd(event) {
+ var instance = event.target.parentElement.children[0].innerText;
+ var id = event.target.parentElement.parentElement.getAttribute("listId");
+
+ // TODO: Check for confirmation if it already exists
+ instances[instance] = subLists[id][instance];
+ event.target.parentElement.parentElement.removeChild(event.target.parentElement);
+ resetHosts();
+ }
+
+ function resetHosts() {
+ document.getElementById("hostsTrustedList").innerHTML = "<div><h3>Instance</h3><div class='comments'></div><div><h3>Trust Value</h3></div></div>";
+ document.getElementById("hostsUntrustedList").innerHTML = "<div><h3>Instance</h3><div class='comments'></div><div><h3>Trust Value</h3></div></div>";
+ document.getElementById("hostsKnownList").innerHTML = "<div><h3>Instance</h3><div class='comments'></div><div><h3>Trust Value</h3></div></div>";
+ var instanceKeys = Object.keys(instances).sort();
+ for(var i = 0; i < instanceKeys.length; i++) {
+ var instance = instanceKeys[i]; //.domain; //.acct.replace( directory[i].username + "@", "" );
+ if(instances[instance].trust == undefined) {
+ instances[instance].trust = 0;
+ }
+
+ var instElem = document.createElement("div");
+ instElem.innerHTML = "<h3>" + instance + "</h3>";
+ instElem.setAttribute("id", "instance-"+instance);
+ instElem.setAttribute("class", "instanceElem");
+
+ var commentsElem = document.createElement("div");
+ commentsElem.setAttribute("class", "comments");
+ commentsElem.innerHTML = "";
+ for(inst in instances[instance].reasons) {
+ commentsElem.innerHTML += inst + " - " + instances[instance].reasons[inst] + "<br/>";
+ }
+ instElem.appendChild(commentsElem);
+
+ var divElem = document.createElement("div");
+ var rangeElem = document.createElement("input");
+ rangeElem.setAttribute("type", "range");
+ rangeElem.setAttribute("min", "-100");
+ rangeElem.setAttribute("max", "100");
+ rangeElem.setAttribute("value",instances[instance].trust);
+ rangeElem.oninput = syncRange;
+ rangeElem.onchange = updateRange;
+ divElem.appendChild(rangeElem);
+
+ var valElem = document.createElement("input");
+ valElem.setAttribute("type", "text");
+ valElem.value = instances[instance].trust;
+// valElem.disabled = true;
+ valElem.onchange = updateVal;
+ divElem.appendChild(valElem);
+ instElem.appendChild(divElem);
+
+/* var headerDiv = document.createElement("div");
+ headerDiv.innerHTML = "<h3>Instance</h3><div><h3>Trust Value</h3></div>";
+ document.getElementById("hostsTrustedList").innerHTML = "";
+ document.getElementById("hostsUntrustedList").innerHTML = "";*/
+ if(instances[instance].trust == 0) {
+ document.getElementById("hostsKnownList").appendChild(instElem);
+ } else if(instances[instance].trust > 0) {
+ document.getElementById("hostsTrustedList").appendChild(instElem);
+ } else {
+ document.getElementById("hostsUntrustedList").appendChild(instElem);
+ }
+ }
+// document.getElementById("hostsKnownOpen").onclick();
+ }
+
+ function openPanel(event) {
+ var listElem = this.parentElement.getElementsByClassName("list")[0];
+ if( listElem.style.display == "block" ) {
+ this.parentElement.getElementsByClassName("list")[0].style.display = "none";
+ this.innerHTML = "+";
+ } else {
+ this.parentElement.getElementsByClassName("list")[0].style.display = "block";
+ this.innerHTML = "-";
+ }
+ }
+
+ function updateRange(event) {
+ var id = this.parentElement.parentElement.parentElement.getAttribute("listId");
+ var instance = this.parentElement.parentElement.firstChild.innerText;
+ if( id == null) {
+ instances[instance].trust = this.value;
+ } else {
+ subLists[id][instance].trust = this.value;
+ }
+ this.parentElement.children[1].value = this.value;
+
+ if(id == null) {
+ var parentPanel = this.parentElement.parentElement.parentElement;
+ var instElem = this.parentElement.parentElement;
+ if( this.value < 0 && parentPanel != document.getElementById("hostsUntrustedList") ) {
+ parentPanel.removeChild(instElem);
+ document.getElementById("hostsUntrustedList").appendChild(instElem);
+ } else if( this.value > 0 && parentPanel != document.getElementById("hostsTrustedList") ) {
+ parentPanel.removeChild(instElem);
+ document.getElementById("hostsTrustedList").appendChild(instElem);
+ } else if( this.value == 0 && parentPanel != document.getElementById("hostsKnownList") ) {
+ parentPanel.removeChild(instElem);
+ document.getElementById("hostsKnownList").appendChild(instElem);
+ }
+ }
+ }
+
+ function syncRange(event) {
+ this.parentElement.children[1].value = this.value;
+ }
+
+ function updateVal(event) {
+ var id = event.target.parentElement.parentElement.getAttribute("listId");
+ var instance = this.parentElement.parentElement.firstChild.innerText;
+ if(this.value < -100) { this.value = -100; }
+ if(this.value > 100) { this.value = 100; }
+ this.parentElement.children[0].value = this.value;
+ instances[instance].trust = this.value;
+
+ if(id == null) {
+ var parentPanel = this.parentElement.parentElement.parentElement;
+ var instElem = this.parentElement.parentElement;
+ if( this.value < 0 && parentPanel != document.getElementById("hostsUntrustedList") ) {
+ parentPanel.removeChild(instElem);
+ document.getElementById("hostsUntrustedList").appendChild(instElem);
+ } else if( this.value > 0 && parentPanel != document.getElementById("hostsTrustedList") ) {
+ parentPanel.removeChild(instElem);
+ document.getElementById("hostsTrustedList").appendChild(instElem);
+ } else if( this.value == 0 && parentPanel != document.getElementById("hostsKnownList") ) {
+ parentPanel.removeChild(instElem);
+ document.getElementById("hostsKnownList").appendChild(instElem);
+ }
+ }
+ }
+
+ function importStr(str = "") {
+ var shade = document.createElement("div");
+ shade.setAttribute("id", "shade");
+ shade.onclick = function() {
+ document.body.removeChild(document.getElementById("shade"));
+ document.body.removeChild(document.getElementById("shade-text"));
+ document.body.removeChild(document.getElementById("shade-text-button"));
+ }
+
+ var text = document.createElement("textarea");
+ text.innerHTML = str;
+ text.setAttribute("id", "shade-text");
+
+ var btn = document.createElement("button");
+ btn.innerHTML = "Import";
+ btn.setAttribute("id", "shade-text-button");
+ btn.onclick = function() {
+ loadStr(document.getElementById("shade-text").value);
+ document.getElementById("shade").onclick();
+ resetHosts();
+ };
+
+ document.body.appendChild(shade);
+ document.body.appendChild(text);
+ document.body.appendChild(btn);
+ }
+
+ function saveStr() {
+ var instancesArr = [];
+ for(instance in instances) {
+ instancesArr.push(instances[instance]);
+ if(typeof instances[instance].reasons == "object") {
+ reasonsExport = [];
+ for(inst in instances[instance].reasons) {
+ var rsn = {};
+ rsn.domain = inst;
+ rsn.text = instances[instance].reasons[inst];
+ reasonsExport.push(rsn);
+ }
+ instancesArr[instancesArr.length-1].reasons = reasonsExport;
+ }
+ }
+
+ var configArr = {};
+ configArr.instances = instancesArr;
+ configArr.srchost = document.getElementById("sourceHost").value;
+ configArr.mulLim = document.getElementById("multiplier-limit").value;
+ configArr.mulSus = document.getElementById("multiplier-suspend").value;
+ configArr.thrLim = document.getElementById("threshold-limit").value;
+ configArr.thrSus = document.getElementById("threshold-suspend").value;
+ return JSON.stringify(configArr);
+ }
+ function loadStr(configJSON) {
+ instances = [];
+ configArr = JSON.parse(configJSON);
+ document.getElementById("sourceHost").value = configArr.srchost;
+ document.getElementById("multiplier-limit").value = configArr.mulLim;
+ document.getElementById("multiplier-suspend").value= configArr.mulSus;
+ document.getElementById("threshold-limit").value = configArr.thrLim;
+ document.getElementById("threshold-suspend").value = configArr.thrSus;
+ for(var i = 0; i < configArr.instances.length; i++) {
+ instances[configArr.instances[i].domain] = configArr.instances[i];
+ if(typeof configArr.instances[i].reasons == "object") {
+ reasonsArr = [];
+ for(j = 0; j < configArr.instances[i].reasons.length; j++) {
+ reasonsArr[configArr.instances[i].reasons[j].domain] =
+ configArr.instances[i].reasons[j].text;
+ }
+ configArr.instances[i].reasons = reasonsArr;
+ }
+ }
+ }
+
+ function exportStr() {
+ importStr(saveStr());
+ }
+
+ function loadCache() {
+ loadStr(localStorage.getItem("config"));
+ resetHosts();
+ }
+
+ function saveCache() {
+ localStorage.setItem("config", saveStr());
+ }
+
+ function checkImpotence(hostList, instance, response = null) {
+ if(typeof instances[instance].impotent != "undefined" && hostList.length > 0) {
+ instance = hostList.pop();
+ checkImpotence(hostList, instance, response);
+ }
+ if(response != null) {
+ if(response.status == "404") {
+ instances[instance].impotent = 1;
+ if(instances[instance].impotent == 1) {
+ document.getElementById("instance-"+instance).style.display = "none";
+ } else {
+ document.getElementById("instance-"+instance).style.display = "block";
+ }
+ } else if(response.status == "200") {
+ instances[instance].impotent = 0;
+ if(instances[instance].impotent == 1) {
+ document.getElementById("instance-"+instance).style.display = "none";
+ } else {
+ document.getElementById("instance-"+instance).style.display = "block";
+ }
+ } else {
+ instances[instance].impotent = 1;
+ if(instances[instance].impotent == 1) {
+ document.getElementById("instance-"+instance).style.display = "none";
+ } else {
+ document.getElementById("instance-"+instance).style.display = "block";
+ }
+ }
+ }
+
+ if(hostList.length == 0) {
+ return;
+ }
+ var instance = hostList.pop();
+ fetch("https://"+instance+"/api/v1/instance/domain_blocks", { signal: AbortSignal.timeout(5000) })
+ .then(response => checkImpotence(hostList, instance, response))
+ .catch(response => checkImpotence(hostList, instance, response));
+ }
+
+ function hideImpotent() {
+ var keys = Object.keys(instances);
+ for(i = 0; i < keys.length; i+=(keys.length/10)) {
+ var sublist = keys.slice(i, i+(keys.length/10));
+ var instance = sublist.pop();
+ checkImpotence( sublist, instance )
+ }
+ }
+
+ async function hideSubdomains() {
+ alert("STUB");
+ }
+
+ function exportCSV() {
+ var data = "";
+ for(instance in instances) {
+ var action = "";
+ if(instances[instance].trust <= document.getElementById('threshold-limit').value) {
+ action = "limit";
+ } else if(instances[instance].trust <= document.getElementById('threshold-suspend').value) {
+ action = "suspend";
+ }
+
+ if(action != "") {
+ data += instance + ",";
+ data += action + ",";
+ data += '"';
+ for(inst in instances[instance].reasons) {
+ data += inst + " - " + instances[instance].reasons[inst] + "\n";
+ }
+ data = data.trim();
+ data += '"\n';
+ }
+ }
+ var elem = document.createElement("a");
+ elem.setAttribute("href", "data:text/plain;charset=utf-8,"
+ + encodeURIComponent(data.trim()));
+ elem.setAttribute('download', 'mastowot.csv');
+ document.body.appendChild(elem);
+ elem.click();
+ document.body.removeChild(elem);
+ }
+ </script>
+ </head>
+ <body>
+ <h1>MastoWoT: A Web-of-Trust inspired defederation station</h1>
+ <!-- Constant iteration of blacklist generation for spam immoliation,
+ without subjugation, now with extra alliteration! -->
+ <!-- Although likely just mental masturbation becaues really is
+ anyone going to use this thing? I doubt it... -->
+ <div id="manPanel">
+ <button id="manPanelOpen" class="panelButton">-</button>
+ <h2>Instructions</h2>
+ <div class="list" style="display: block">
+ <p><strong>This tool is intended for Mastodon instance administrators</strong></p>
+ <p>You can still play around with it even if you aren't a server admin, but
+ don't complain to me that you can't figure out how to work it :)</p>
+ <p>This tool is designed to <em>suggest</em> a blocklist that you may
+ want to configure on your Mastodon instance. The goal is to create a
+ system simpler than scrolling #FediBlock, more proactive than relying
+ entirely on user reports, and more federated than distributing blocklists
+ from a central authority.</p>
+ <p>To start, enter your instance's domain (no 'https://', no page, just the domain)
+ into the box above the synchronize button
+ on the right side menu, and click "Synchronize". This will make two calls
+ to your server's API to fetch the known peers and the block list. This
+ WILL require that your instance is configured to share this information.
+ This can be configured in your instance settings, under Site Settings,
+ enable 'publish list of discovered servers' and 'enable profile directory'.
+ Also set "show domain blocks" and "show rationale" as "to everyone".</p>
+ <p>Once you synchronie, any domains you have already blocked will appear
+ under Untrusted Hosts. All other known peers will appear under Known Hosts.
+ Now, you need to find some hosts you trust in the Known Hosts list and move
+ their slider to the right. The slider moves between -100 and 100, and once
+ you select a value it will update in the text box (you can also enter a
+ value into the box directly). This should
+ represent how much you "trust" this server, with -100 meaning you think this
+ server is "evil" and 100 meaning it is to be trusted entirely (at least for
+ the purposes of generating a blocklist). As you set values, these hosts will
+ move to Trusted Hosts or Untrusted Hosts accordingly. I do NOT recommend
+ trying to set a value for every single instance in the list. Search the page
+ for instances that you are already familiar with instead.</p>
+ <p>Once you have added a few trusted instances, click "Process" from the right
+ side menu. This will fetch the block list from each instance on your trusted
+ list. A host that appears on any of these blocklists will receive a negative
+ trust value equal to the trust value of the hosts that have blocked it,
+ multiplied by the appropriate multiplier values (configured in the side
+ menu), averaged by dividing it by the total number of hosts in the list.</p>
+ <p>Finally, you can use the "Export for Mastodon" button from the side menu
+ to export a blocklist as a CSV file, which you can then import into your
+ instance using the script provided below. This export will be
+ generated based on the Thresholds values set on the right side menu. Any
+ hosts with a trust value equal to or below the suspend threshold will be
+ suspended; any remaining hosts with a trust value equal to or below the
+ limit thershold will be limited.</p>
+ <p>Also please be aware that some instances may choose to obfuscate certain
+ URLs from their block list. Please don't do that. <strong>This tool cannot
+ work with obfuscated URLs.</strong></p>
+ <p>The remaining options are mentioned below:</p>
+ <ul>
+ <li>Hide Impotents: Tries to fetch a blocklist from all hosts in the list.
+ If no blocklist can be downloaded, the host will be hidden (They will NOT
+ be removed from the list, only hidden from display.) There is no point
+ adding one of these hosts to your trusted list if they do not provide a
+ their block list.</li>
+ <li>Find Enemies of Enemies: Creates a new list of hosts that are blocked
+ by the hosts on your untrusted list. The hosts on this list will NOT be
+ considered trusted or untrusted until you click the 'add' button to move
+ them into the main list with the given trust value.</li>
+ <li>Find Friends of Friends: Creates a new list of hosts that are in the
+ directory of the hosts in your trusted list. Trust values will be based
+ on both the trust value of the trusted host and the number of accounts
+ in its directory that are hosted at the this instance.</li>
+ <li>FInd Associates of Enemies: Creates a new list of hosts that are in the
+ directory of the hosts in your untrusted list. Trust values will be based
+ on both the trust value of the untrusted host and the number of accounts
+ in its directory that are hosted at the this instance.</li>
+ <li>Load from cache: Load instances list from the browser cache</li>
+ <li>Save to cache: Save instances list to your browser cache</li>
+ <li>Export to you: Show the instances list as a JSON string</li>
+ <li>Import from you: Import a JSON string exported previously</li>
+ </ul>
+
+<hr/>
+<pre>
+# IMPORT SCRIPT
+# I realize Mastodon already has an import function, but as far as I can tell, that
+# does not allow you to include the comments. This script will.
+
+# This script assumes the export file is available in the current directory
+# and named 'mastowot.csv'
+
+# This should be your mastodon API token with admin access
+# (Generate from your admin user's development menu. Requires admin;write permissions)
+ADMIN_ACCESS_TOKEN=""
+# And your instance hostname of course
+MASTODON_HOST="https://"
+
+ts="`date +%Y%m%d-%H:%M:%S`"
+awk -v RS='"\n' \
+ -v FS="," \
+ -v authtoken="$ADMIN_ACCESS_TOKEN" \
+ -v tgthost="$MASTODON_HOST" \
+ -v timestamp="$ts" \
+'{
+ system("curl -X POST -H \"Authorization: Bearer "authtoken"\" \
+ -F \"domain="$1"\" -F \"private_comment=Updated by MastoWoT "timestamp"\" \
+ -F \"public_comment="$2"\" \
+ "tgthost"/api/v1/admin/domain_blocks");
+}' mastowot.csv
+
+</pre>
+<hr/>
+ <pre>
+TODO:
+ Styling:
+ Color chooser, high contrast mode
+ Sort lists by name or trust level
+ Determine if blocked hosts have/had direct associations? and when?
+
+</pre>
+ <p>Ultimately I do think this would make more sense as a shell script run
+ via cron job...but I think this is a better way to display the concept,
+ so that version is part two :)</p>
+ </div>
+ </div>
+ <div id="hostsPanel">
+ <div id="hostsTrusted" class="subpanel">
+ <button id="hostsTrustedOpen" class="panelButton">+</button>
+ <h2>Trusted Hosts</h2>
+ <div id="hostsTrustedList" class="list">
+ <div>
+ <h3>Instance</h3>
+ <div>
+ <h3>Trust Value</h3>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div id="hostsUntrusted" class="subpanel">
+ <button id="hostsUntrustedOpen" class="panelButton">+</button>
+ <h2>Untrusted Hosts</h2>
+ <div id="hostsUntrustedList" class="list">
+ <div>
+ <h3>Instance</h3>
+ <div>
+ <h3>Trust Value</h3>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div id="hostsKnown" class="subpanel">
+ <button id="hostsKnownOpen" class="panelButton">+</button>
+ <h2>Known Hosts</h2>
+ <div id="hostsKnownList" class="list">
+ <div>
+ <h3>Instance</h3>
+ <div>
+ <h3>Trust Value</h3>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div id="settingsPanel">
+ <input type="text" id="sourceHost" />
+ <button id="syncButton" onclick="synchronize()">Synchronize</button>
+ <br/>
+ <button id="processButton" onclick="process()">Process</button>
+ <br/>
+ <button onclick="hideImpotent()" >Hide impotents</button>
+ <button id="enemiesOfEnemies" onclick="enemiesOfEnemies()">Find Enemies of Enemies</button>
+ <button id="friendsOfFriends" onclick="friendsOfFriends()">Find Friends of Friends</button>
+ <button id="assocWithEnemies" onclick="assocWithEnemies()">Find Associates of Enemies</button>
+ <br/>
+ <button onclick="loadCache()" >Load from cache</button>
+ <button onclick="saveCache()" >Save to cache</button>
+ <br/>
+ <button onclick="exportStr()" >Export to you</button>
+ <button onclick="importStr()" >Import from you</button>
+ <br/>
+ <button onclick="exportCSV()" >Export for Mastodon</button>
+ <br/>
+<!-- <button onclick="hideSubdomains()" >Hide Subdomains</button> -->
+<!-- <button>Share</button> -->
+ <div>
+ <h2>Multipliers</h2>
+ <label>Limit</label>
+ <input type="text" value="1" id="multiplier-limit" />
+ <label>Suspend</label>
+ <input type="text" value="2" id="multiplier-suspend" />
+ </div>
+ <div>
+ <h2>Thresholds</h2>
+ <label>Limit</label>
+ <input type="text" value="-50" id="threshold-limit" />
+ <label>Suspend</label>
+ <input type="text" value="-100" id="threshold-suspend" />
+ </div>
+ </div>
+ <script>init();</script>
+ </body>
+</html>
--- /dev/null
+body {
+ background-color: black;
+ padding: 0px;
+ margin: 0px;
+ color: #CAA;
+ font-family: "Audiowide", "Sans";
+}
+
+h1 {
+ width: 100%;
+ text-style: italic;
+ text-align: center;
+}
+
+h2 {
+ display: inline-block;
+ margin: .3em .5em .5em .5em;
+ vertical-align: middle;
+}
+
+.panelbutton {
+ display: inline-block;
+}
+
+#settingsPanel #sourceHost {
+ display: inline-block;
+ width: 100%;
+}
+
+#hostsPanel {
+ display: inline-block;
+ width: calc( 70% - 10px );
+ min-width: 30em;
+ height: calc(100% - 3em);
+ margin: 0px;
+ vertical-align: top;
+}
+
+.subpanel, #manPanel {
+ padding: 0em 1em;
+ border: 3px solid #C00;
+ border-radius: 1em;
+ background-color: #600;
+ margin-bottom: 1em;
+}
+
+#settingsPanel {
+ display: inline-block;
+ width: calc( 30% - 10px - 2em);
+ min-width: 10em;
+ height: calc(100% - 2em);
+ background-color: #400;
+ border: 3px solid #C00;
+ margin: 0px;
+ border-radius: 1em;
+ padding: 1em;
+ vertical-align: top;
+}
+
+#settingsPanel a {
+ display: block;
+ width: 100%;
+ text-align: center;
+ margin-bottom: 1em;
+ font-weight: bold;
+ color: #C00;
+}
+
+#settingsPanel button {
+ display: block;
+ width: 100%;
+}
+
+#settingsPanel div {
+ border: 1px solid black;
+ border-radius: 1em;
+ padding: 0em 0em 1em 0em;
+ margin-top: 1em;
+}
+
+#settingsPanel h2 {
+ width: 100%;
+}
+
+#settingsPanel label {
+ margin-left: 10%;
+ display: inline-block;
+ width: 40%;
+}
+
+#settingsPanel input {
+ display: inline-block;
+ width: 40%;
+}
+
+#shade {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ background-color: black;
+ opacity: 0.8;
+ z-index: 100;
+ left: 0px;
+ top: 0px;
+}
+
+#shade-text {
+ position: fixed;
+ width: 40%;
+ height: 40%;
+ left: 30%;
+ top: 30%;
+ z-index: 150;
+}
+
+#shade-text-button {
+ position: fixed;
+ width: 40%;
+ height: 5%;
+ left: 30%;
+ top: 70%;
+ z-index: 150;
+ font-size: 3vh;
+}
+
+.subpanel .list {
+ display: none;
+}
+
+.subpanel .list .instanceElem:nth-child(2n+3) {
+ background-color: #C11;
+}
+
+.subpanel .list div {
+ display: block;
+ position: relative;
+ border-bottom: 1px solid black;
+}
+
+.subpanel .list div h3 {
+ display: inline-block;
+ width: calc( 50% - 20em );
+ margin: 0em;
+ padding: 0em;
+ overflow: scroll;
+}
+
+.subpanel .list div div {
+ display: inline-block;
+ position: relative;
+ width: 20em;
+ vertical-align: middle;
+}
+
+.subpanel .list div .comments {
+ display: inline-block;
+ margin: none;
+ width: 48%;
+ border: none;
+}
+
+.subpanel .list div div input[type=text] {
+ display: inline-block;
+ width: 3em;
+ vertical-align: middle;
+}
+
+.subpanel .list div div input[type=range] {
+ display: inline-block;
+ width: 17em;
+ vertical-align: middle;
+}
+
+.subpanel .list div div h3 {
+ width: 100%;
+}
+
+.subpanel .list div:first-child h3 {
+ font-weight: bold;
+ text-decoration: underline;
+}
--- /dev/null
+<html>
+ <head>
+ <title>MastoWoT</title>
+ <link rel="stylesheet" href="./mastowot.css" type="text/css" />
+ <script src="./mastowot.js" type="text/javascript"></script>
+ <!-- NOTE TO DEVS AND SUCH: I'll probably add an open source license to this and
+ get it in my git repo at some point, but at the moment I have not done that.
+ If you'd like to share/modify/etc, please let me know.
+ @admin@mastodon.slightlycyberpunk.com -->
+ </head>
+ <body>
+ <h1>MastoWoT: A Web-of-Trust inspired defederation station</h1>
+ <!-- Constant iteration of blacklist generation for spam immoliation,
+ without subjugation, now with extra alliteration! -->
+ <!-- Although likely just mental masturbation becaues really is
+ anyone going to use this thing? I doubt it... -->
+
+ <div id="manPanel">
+ <button id="manPanelOpen" class="panelButton">-</button>
+ <h2>Instructions</h2>
+ <div class="list" style="display: block">
+ <p><strong>This tool is intended for Mastodon instance administrators</strong></p>
+ <p>You can still play around with it even if you aren't a server admin, but
+ don't complain to me that you can't figure out how to import the lists :)</p>
+ <p>This tool is designed to <em>suggest</em> a blocklist that you may
+ want to configure on your Mastodon instance. The goal is to create a
+ system simpler than scrolling #FediBlock, more proactive than relying
+ entirely on user reports, and more federated than distributing blocklists
+ from a central authority.</p>
+ <p>To start, enter your instance's domain (no 'https://', no page, just the domain)
+ into the box above the synchronize button
+ on the right side menu, and click "Synchronize". This will make two calls
+ to your server's API to fetch the known peers and the block list. This
+ WILL require that your instance is configured to share this information.
+ This can be configured in your instance settings, under Site Settings,
+ enable 'publish list of discovered servers' and 'enable profile directory'.
+ Also set "show domain blocks" and "show rationale" as "to everyone".</p>
+ <p>Once you synchronize, any domains you have already blocked will appear
+ under Untrusted Hosts. All other known peers will appear under Known Hosts.
+ Now, you need to find some hosts you trust in the Known Hosts list and move
+ their slider to the right. The slider moves between -100 and 100, and once
+ you select a value it will update in the text box (you can also enter a
+ value into the box directly). This should
+ represent how much you "trust" this server, with -100 meaning you think this
+ server is "evil" and 100 meaning it is to be trusted entirely (at least for
+ the purposes of generating a blocklist). As you set values, these hosts will
+ move to Trusted Hosts or Untrusted Hosts accordingly. I do NOT recommend
+ trying to set a value for every single instance in the list. Search the page
+ for instances that you are already familiar with instead.</p>
+ <p>Once you have added a few trusted instances, click "Process" from the right
+ side menu. This will fetch the block list from each instance on your trusted
+ list. A host that appears on any of these blocklists will receive a negative
+ trust value equal to the trust value of the hosts that have blocked it,
+ multiplied by the appropriate multiplier values (configured in the side
+ menu), averaged by dividing it by the total number of hosts in the list.</p>
+ <p>Finally, you can use the "Export for Mastodon" button from the side menu
+ to export a blocklist as a CSV file, which you can then import into your
+ instance using the script provided below. This export will be
+ generated based on the Thresholds values set on the right side menu. Any
+ hosts with a trust value equal to or below the suspend threshold will be
+ suspended; any remaining hosts with a trust value equal to or below the
+ limit thershold will be limited.</p>
+ <p>Also please be aware that some instances may choose to obfuscate certain
+ URLs from their block list. Please don't do that. <strong>This tool cannot
+ work with obfuscated URLs.</strong></p>
+ <p>The remaining options are mentioned below:</p>
+ <ul>
+ <li>Hide Impotents: Tries to fetch a blocklist from all hosts in the list.
+ If no blocklist can be downloaded, the host will be hidden (They will NOT
+ be removed from the list, only hidden from display.) There is no point
+ adding one of these hosts to your trusted list if they do not provide a
+ their block list.</li>
+ <li>Hide Subdomains: Hides any instances from the list which are subdomains
+ of another entry in the list. Blocking the parent domain blocks all
+ subdomains, so you may not always need to handle each subdomain separately.</li>
+ <li>Find Enemies of Enemies: Creates a new list of hosts that are blocked
+ by the hosts on your untrusted list. The hosts on this list will NOT be
+ considered trusted or untrusted until you click the 'add' button to move
+ them into the main list with the given trust value.</li>
+ <li>Find Associates of Enemies: Creates a new list of hosts that are in the
+ directory of the hosts in your untrusted list. Trust values will be based
+ on both the trust value of the untrusted host and the number of accounts
+ in its directory that are hosted at the this instance. (This will take a
+ while to process.)</li>
+ <li>Find Associates of Friends: Creates a new list of hosts that are in the
+ directory of the hosts in your trusted list. Trust values will be based
+ on both the trust value of the trusted host and the number of accounts
+ in its directory that are hosted at the this instance. (This will take a
+ while to process.)</li>
+ <li>Load from cache: Load instances list from the browser cache</li>
+ <li>Save to cache: Save instances list to your browser cache</li>
+ <li>Export to you: Show the instances list as a JSON string</li>
+ <li>Import from you: Import a JSON string exported previously</li>
+ </ul>
+
+<hr/>
+<pre>
+# IMPORT SCRIPT
+# I realize Mastodon already has an import function, but as far as I can tell, that
+# does not allow you to include the comments. This script will.
+
+# This script assumes the export file is available in the current directory
+# and named 'mastowot.csv'
+
+# This should be your mastodon API token with admin access
+# (Generate from your admin user's development menu. Requires admin;write permissions)
+ADMIN_ACCESS_TOKEN=""
+# And your instance hostname of course
+MASTODON_HOST="https://"
+
+ts="`date +%Y%m%d-%H:%M:%S`"
+awk -v RS='"\n' \
+ -v FS="," \
+ -v authtoken="$ADMIN_ACCESS_TOKEN" \
+ -v tgthost="$MASTODON_HOST" \
+ -v timestamp="$ts" \
+'{
+ system("curl -X POST -H \"Authorization: Bearer "authtoken"\" \
+ -F \"domain="$1"\" -F \"private_comment=Updated by MastoWoT "timestamp"\" \
+ -F \"public_comment="$2"\" \
+ "tgthost"/api/v1/admin/domain_blocks");
+}' mastowot.csv
+
+</pre>
+<hr/>
+ <pre>
+TODO:
+ Styling:
+ Color chooser, high contrast mode
+ Maybe make the UI not look like....this.
+ Determine if blocked hosts have/had direct associations, and when?
+ (ie, see if they blocked this person or if they got the block from elsewhere)
+ Explain how this shit works a little better, maybe a video?
+ (Maybe a *good* video...)
+</pre>
+ <p>[ Ultimately I do think this would make more sense as a shell script run
+ via cron job...but I think this is a better way to display the concept,
+ so that version is part two :) ]</p>
+ <p>FYI, this site runs entirely on your machine and should not send ANY of
+ your data to my server. This page is made of three files -- mastowot.html,
+ mastowot.js, and mastowot.css. You can download all three and run them from
+ your local system, no server required. You can copy them to your own server
+ to share. They are licensed under the GPL v3, and about 1000 lines of code
+ last I checked if you want to review it.</p>
+ </div>
+ </div>
+ <div id="hostsPanel">
+ <div id="hostsTrusted" class="subpanel">
+ <button id="hostsTrustedOpen" class="panelButton">+</button>
+ <h2>Trusted Hosts</h2>
+ <div id="hostsTrustedList" class="list">
+ <div>
+ <h3>Instance</h3>
+ <div>
+ <h3>Trust Value</h3>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div id="hostsUntrusted" class="subpanel">
+ <button id="hostsUntrustedOpen" class="panelButton">+</button>
+ <h2>Untrusted Hosts</h2>
+ <div id="hostsUntrustedList" class="list">
+ <div>
+ <h3>Instance</h3>
+ <div>
+ <h3>Trust Value</h3>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div id="hostsKnown" class="subpanel">
+ <button id="hostsKnownOpen" class="panelButton">+</button>
+ <h2>Known Hosts</h2>
+ <div id="hostsKnownList" class="list">
+ <div>
+ <h3>Instance</h3>
+ <div>
+ <h3>Trust Value</h3>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div id="settingsPanel">
+ <input type="text" id="sourceHost" />
+ <button id="syncButton" onclick="synchronize()">Synchronize</button>
+ <br/>
+ <button id="processButton" onclick="process()">Process</button>
+ <br/>
+ <button onclick="hideImpotent()" >Hide Impotents</button>
+ <button onclick="hideSubdomains()" >Hide Subdomains</button>
+ <button id="enemiesOfEnemies" onclick="enemiesOfEnemies()">Find Enemies of Enemies</button>
+ <button id="assocWithEnemies" onclick="assocWithEnemies()">Find Associates of Enemies</button>
+ <button id="friendsOfFriends" onclick="friendsOfFriends()">Find Associates of Friends</button>
+ <br/>
+ <button onclick="loadCache()" >Load from cache</button>
+ <button onclick="saveCache()" >Save to cache</button>
+ <br/>
+ <button onclick="exportStr()" >Export to you</button>
+ <button onclick="importStr()" >Import from you</button>
+ <br/>
+ <button onclick="exportCSV()" >Export for Mastodon</button>
+ <br/>
+<!-- <button onclick="hideSubdomains()" >Hide Subdomains</button> -->
+<!-- <button>Share</button> -->
+ <div>
+ <h2>Multipliers</h2>
+ <label>Limit</label>
+ <input type="text" value="1" id="multiplier-limit" />
+ <label>Suspend</label>
+ <input type="text" value="2" id="multiplier-suspend" />
+ </div>
+ <div>
+ <h2>Thresholds</h2>
+ <label>Limit</label>
+ <input type="text" value="-50" id="threshold-limit" />
+ <label>Suspend</label>
+ <input type="text" value="-100" id="threshold-suspend" />
+ </div>
+ </div>
+ <script>init();</script>
+ </body>
+</html>
--- /dev/null
+/* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
+ * mastowot.js
+ * version 0.2
+ * updated 2023-01-17
+ * developed by Slightly Cyberpunk
+ * mastodon: @admin@mastodon.slightlycyberpunk.com
+ * Copyright 2023 by Slightly Cyberpunk
+ * Released under the GPL v3 only
+ */
+
+/*
+ * INITIALIZATIONS
+ */
+var instances = []; // Main list of instances (trusted/untrusted/known)
+var subLists = []; // Additional lists (EOE, AOE, FOF, etc)
+var sortFuncArr = []; // Retain the sorting information for each list
+
+function init() {
+ var buttons = document.getElementsByClassName("panelButton");
+ for(var i = 0; i < buttons.length; i++) {
+ buttons[i].onclick = openPanel;
+ }
+}
+
+/*
+ * SUPPORTING FUNCTIONS
+ */
+
+// Sort a list alphabetically by domain name
+function sortDomains(direction, a, b) {
+ if(direction > 0) {
+ return a > b;
+ } else {
+ return a < b;
+ }
+}
+
+// Sort a list by trust level
+// This one takes a list as input because we store by instance name
+// So you can sort by domain just by comparing the key -- it is the domain
+// But to sort by trust level you have to look up the value for that key
+// So you have to know which list it is in
+function sortTrust(direction, list, a, b) {
+ return (list[a].trust - list[b].trust) * direction;
+}
+
+// Create or load the string representation used to import/export
+// JSON doesn't really handle associative arrays
+// So those have to be converted first
+function saveStr() {
+ var instancesArr = [];
+ for(instance in instances) {
+ instancesArr.push(instances[instance]);
+ if(typeof instances[instance].reasons == "object") {
+ reasonsExport = [];
+ for(inst in instances[instance].reasons) {
+ var rsn = {};
+ rsn.domain = inst;
+ rsn.text = instances[instance].reasons[inst];
+ reasonsExport.push(rsn);
+ }
+ instancesArr[instancesArr.length-1].reasons = reasonsExport;
+ }
+ }
+
+ var configArr = {};
+ configArr.instances = instancesArr;
+ configArr.srchost = document.getElementById("sourceHost").value;
+ configArr.mulLim = document.getElementById("multiplier-limit").value;
+ configArr.mulSus = document.getElementById("multiplier-suspend").value;
+ configArr.thrLim = document.getElementById("threshold-limit").value;
+ configArr.thrSus = document.getElementById("threshold-suspend").value;
+ return JSON.stringify(configArr);
+}
+function loadStr(configJSON) {
+ instances = [];
+ configArr = JSON.parse(configJSON);
+ document.getElementById("sourceHost").value = configArr.srchost;
+ document.getElementById("multiplier-limit").value = configArr.mulLim;
+ document.getElementById("multiplier-suspend").value= configArr.mulSus;
+ document.getElementById("threshold-limit").value = configArr.thrLim;
+ document.getElementById("threshold-suspend").value = configArr.thrSus;
+ for(var i = 0; i < configArr.instances.length; i++) {
+ instances[configArr.instances[i].domain] = configArr.instances[i];
+ if(typeof configArr.instances[i].reasons == "object") {
+ reasonsArr = [];
+ for(j = 0; j < configArr.instances[i].reasons.length; j++) {
+ reasonsArr[configArr.instances[i].reasons[j].domain] =
+ configArr.instances[i].reasons[j].text;
+ }
+ configArr.instances[i].reasons = reasonsArr;
+ }
+ }
+}
+
+// Creates a pool of threads checking if each domain is impotent
+// And setting the 'impotent' property accordingly
+// A host is impotent if it returns an error or a 404 trying to check its blocklist
+// This function is recursive, to act as a thread pool
+// You pass it a set of instances, it will call itself until the list is exhausted
+function checkImpotence(hostList, instance, response = null) {
+ // If this host already has the impotent property set, don't re-check it
+ if(typeof instances[instance].impotent != "undefined" && hostList.length > 0) {
+ instance = hostList.pop();
+ checkImpotence(hostList, instance, response);
+ }
+ // Set the impotence property based on the response from the last fetch
+ // And remove impotent doments from the list by setting display to none
+ if(response != null) {
+ if(response.status == "404") {
+ instances[instance].impotent = 1;
+ if(instances[instance].impotent == 1) {
+ document.getElementById("instance-"+instance).style.display = "none";
+ } else {
+ document.getElementById("instance-"+instance).style.display = "block";
+ }
+ } else if(response.status == "200") {
+ instances[instance].impotent = 0;
+ if(instances[instance].impotent == 1) {
+ document.getElementById("instance-"+instance).style.display = "none";
+ } else {
+ document.getElementById("instance-"+instance).style.display = "block";
+ }
+ } else {
+ instances[instance].impotent = 1;
+ if(instances[instance].impotent == 1) {
+ document.getElementById("instance-"+instance).style.display = "none";
+ } else {
+ document.getElementById("instance-"+instance).style.display = "block";
+ }
+ }
+ }
+
+ // If there's nothing left on the list, stop
+ if(hostList.length == 0) {
+ return;
+ }
+
+ // Fetch the block list for the next instance on the list
+ var instance = hostList.pop();
+ fetch("https://"+instance+"/api/v1/instance/domain_blocks", { signal: AbortSignal.timeout(5000) })
+ .then(response => checkImpotence(hostList, instance, response))
+ .catch(response => checkImpotence(hostList, instance, response));
+}
+
+
+/*
+ * MAIN ACTION FUNCTIONS
+ */
+async function synchronize() {
+ instances = [];
+
+ // Configure each list for default sort order
+ var lists = Array("hostsTrustedList", "hostsUntrustedList", "hostsKnownList",
+ "hostseoeList", "hostsaoeList", "hostsfofList");
+ for(var i = 0; i < lists.length; i++) {
+ var listId = lists[i]; //.getAttribute("id");
+ sortFuncArr[listId] = {};
+ sortFuncArr[listId].direction = 1;
+ sortFuncArr[listId].function = function(a,b) {
+ return sortDomains(1,a,b);};
+ }
+
+ // Synchronize the known hosts list
+ var sourceInstance = "https://" + document.getElementById("sourceHost").value;
+ var f1 = fetch(sourceInstance + "/api/v1/instance/peers")
+ .then(response => response.json())
+ .then(peers => {
+ for(var i = 0; i < peers.length; i++) {
+ var instance = peers[i];
+
+ if( instances[instance] == null ) {
+ instances[instance] = new Object();
+ instances[instance].domain = instance;
+ instances[instance].reasons = [];
+ instances[instance].trust = 0;
+ }
+ }
+ });
+
+ // Synchronize the untrusted hosts list
+ var f2 = fetch(sourceInstance + "/api/v1/instance/domain_blocks")
+ .then(response => response.json())
+ .then(blocklist => {
+ console.log(blocklist);
+
+ for(var i = 0; i < blocklist.length; i++) {
+ instance = blocklist[i].domain;
+ if(instance.indexOf("*") != -1) {
+ continue;
+ }
+ if(instances[instance] == null) {
+ instances[instance] = new Object();
+ instances[instance].domain = instance;
+ instances[instance].reasons = [];
+ }
+
+ if( blocklist[i].comment == null ) {
+ blocklist[i].comment = "[ No comments provided ]"
+ }
+ if( blocklist[i].severity == "silence" ) {
+ instances[instance].trust = document.getElementById("threshold-suspend").value;
+ instances[instance].reasons[instance] = "SILENCED: "+blocklist[i].comment;
+ } else {
+ instances[instance].trust = document.getElementById("threshold-limit").value;
+ instances[instance].reasons[instance] = "LIMITED: "+blocklist[i].comment;
+ }
+ }
+ });
+
+ // Re-render the lists once both lists are loaded (or failed)
+ Promise.allSettled([f1, f2])
+ .then(resetHosts);
+}
+
+async function process() {
+ document.getElementById("processButton").disabled = true;
+ var total = 0;
+ // Loop over all trusted instances and fetch the block list
+ // Multiply the source host's trust value by the multiplier and add to subTrust counter
+ for(instance in instances) {
+ if(instances[instance].trust > 0) {
+ try {
+ await fetch("https://"+instance+"/api/v1/instance/domain_blocks")
+ .then(response => response.json())
+ .then(blocklist => {
+ for(var i = 0; i < blocklist.length; i++) {
+ inst = blocklist[i].domain;
+ if(inst.indexOf("*") != -1) {
+ continue;
+ }
+ if(instances[inst] == null) {
+ instances[inst] = new Object();
+ instances[inst].domain = inst;
+ instances[inst].reasons = [];
+ }
+
+ if(typeof instances[inst].subtrust == "undefined") {
+ instances[inst].subtrust = 0;
+ }
+ if( blocklist[i].comment == null ) {
+ blocklist[i].comment = "[ No comments provided ]"
+ }
+ if( blocklist[i].severity == "silence" ) {
+ instances[inst].subtrust -= instances[instance].trust *
+ parseFloat(document.getElementById("multiplier-suspend").value);
+ instances[inst].reasons[instance] = "SILENCED: "+blocklist[i].comment;
+ } else {
+ instances[inst].subtrust -= instances[instance].trust *
+ parseFloat(document.getElementById("multiplier-limit").value);
+ instances[inst].reasons[instance] = "LIMITED: "+blocklist[i].comment;
+ }
+ }
+ total++;
+ });
+ } catch(e) {
+ console.log(e);
+ }
+ }
+ }
+
+ // Normalize trust values by dividing subTrust by the total number of lists checked
+ for(instance in instances) {
+ if(typeof instances[instance].subtrust != "undefined") {
+ instances[instance].trust = instances[instance].subtrust / total;
+ if(instances[instance].trust > 100) { instances[instance].trust = 100; }
+ if(instances[instance].trust < -100) { instances[instance].trust = -100; }
+ }
+ }
+ resetHosts();
+ document.getElementById("processButton").disabled = false;
+}
+
+// Generates the 'Associating with Enemies" list
+// Gets the instances of the 800 users that each trusted instance has
+// communicated with most recently
+async function assocWithEnemies() {
+ document.getElementById("assocWithEnemies").disabled = true;
+ var total = 0;
+ subLists.aoe = [];
+ for(instance in instances) {
+ if(instances[instance].trust < 0) {
+ try {
+ var c = 0;
+ while(c < 10) {
+ await fetch("https://"+instance+"/api/v1/directory?limit=80&offset=0", {signal: AbortSignal.timeout(5000)})
+ .then(response => response.json())
+ .then(directory => {
+ if(directory.length < 80) {
+ c = 10;
+ }
+ for(var i = 0; i < directory.length; i++) {
+ inst = directory[i].acct.split("@")[1];
+ if(inst == null || inst.indexOf("*") != -1) {
+ continue;
+ }
+ if(subLists.aoe[inst] == null) {
+ subLists.aoe[inst] = new Object();
+ subLists.aoe[inst].domain = inst;
+ subLists.aoe[inst].reasons = [];
+ subLists.aoe[inst].trust = 0;
+ subLists.aoe[inst].aoesubtrust = 0;
+ }
+
+ subLists.aoe[inst].aoesubtrust += parseFloat(instances[instance].trust);
+ if(subLists.aoe[inst].aoesubtrust < total) {
+ total = subLists.aoe[inst].aoesubtrust;
+ }
+ }
+ });
+ c++;
+ }
+ } catch(e) {
+ console.log(e);
+ }
+ }
+ }
+
+ // Normalize trust values
+ for(instance in subLists.aoe) {
+ if(typeof subLists.aoe[instance].aoesubtrust != "undefined") {
+ subLists.aoe[instance].trust = (subLists.aoe[instance].aoesubtrust * 100) / total;
+ if(subLists.aoe[instance].trust > 100) { subLists.aoe[instance].trust = 100; }
+ if(subLists.aoe[instance].trust < -100) { subLists.aoe[instance].trust = -100; }
+ }
+ }
+ addTable("aoe", "Associates of Enemies", subLists.aoe);
+ document.getElementById("assocWithEnemies").disabled = false;
+}
+
+// Generates the 'Associating with Friends' list
+// Gets the instances of the 800 users that each trusted instance has
+// communicated with most recently.
+async function friendsOfFriends() {
+ document.getElementById("friendsOfFriends").disabled = true;
+ var total = 0;
+ subLists.fof = [];
+ for(instance in instances) {
+ if(instances[instance].trust > 0) {
+ try {
+ var c = 0;
+ while(c < 10) {
+ await fetch("https://"+instance+"/api/v1/directory?limit=80&offset="+(80*c),
+ {signal: AbortSignal.timeout(5000) })
+ .then(response => response.json())
+ .then(directory => {
+ if(directory.length < 80) {
+ c = 10;
+ }
+ for(var i = 0; i < directory.length; i++) {
+ inst = directory[i].acct.split("@")[1];
+ if(inst == null || inst.indexOf("*") != -1) {
+ continue;
+ }
+ if(subLists.fof[inst] == null) {
+ subLists.fof[inst] = new Object();
+ subLists.fof[inst].domain = inst;
+ subLists.fof[inst].reasons = [];
+ subLists.fof[inst].trust = 0;
+ subLists.fof[inst].fofsubtrust = 0;
+ }
+
+ subLists.fof[inst].fofsubtrust += parseFloat(instances[instance].trust);
+ if(subLists.fof[inst].fofsubtrust > total) {
+ total = subLists.fof[inst].fofsubtrust;
+ }
+ }
+ });
+ c++;
+ }
+ } catch(e) {
+ console.log(e);
+ }
+ }
+ }
+ // Normalize trust values
+ for(instance in subLists.fof) {
+ if(typeof subLists.fof[instance].fofsubtrust != "undefined") {
+ subLists.fof[instance].trust = (int)(subLists.fof[instance].fofsubtrust * 100) / total;
+ if(subLists.fof[instance].trust > 100) { subLists.fof[instance].trust = 100; }
+ if(subLists.fof[instance].trust < -100) { subLists.fof[instance].trust = -100; }
+ }
+ }
+ addTable("fof", "Friends of Friends", subLists.fof);
+ document.getElementById("friendsOfFriends").disabled = false;
+}
+
+// Generates the 'Enemies of Enemies' list
+// Get the instances blocked by the untrusted instances
+async function enemiesOfEnemies() {
+ document.getElementById("enemiesOfEnemies").disabled = true;
+ var total = 0;
+ subLists.eoe = [];
+ for(instance in instances) {
+ if(instances[instance].trust < 0) {
+ try {
+ await fetch("https://"+instance+"/api/v1/instance/domain_blocks", { signal: AbortSignal.timeout(5000) })
+ .then(response => response.json())
+ .then(blocklist => {
+ for(var i = 0; i < blocklist.length; i++) {
+ inst = blocklist[i].domain;
+ if(inst.indexOf("*") != -1) {
+ continue;
+ }
+ if(subLists.eoe[inst] == null) {
+ subLists.eoe[inst] = new Object();
+ subLists.eoe[inst].domain = inst;
+ subLists.eoe[inst].reasons = [];
+ subLists.eoe[inst].eoesubtrust = 0;
+ }
+
+ if( blocklist[i].comment == null ) {
+ blocklist[i].comment = "[ No comments provided ]"
+ }
+ if( blocklist[i].severity == "silence" ) {
+ subLists.eoe[inst].eoesubtrust -= instances[instance].trust *
+ parseFloat(document.getElementById("multiplier-suspend").value);
+ subLists.eoe[inst].reasons[instance] = "SILENCED: "+blocklist[i].comment;
+ } else {
+ subLists.eoe[inst].eoesubtrust -= instances[instance].trust *
+ parseFloat(document.getElementById("multiplier-limit").value);
+ subLists.eoe[inst].reasons[instance] = "LIMITED: "+blocklist[i].comment;
+ }
+ }
+ total++;
+ });
+ } catch(e) {
+ console.log(e);
+ }
+ }
+ }
+ // Normalize trust values
+ for(instance in subLists.eoe) {
+ if(typeof subLists.eoe[instance].eoesubtrust != "undefined") {
+ subLists.eoe[instance].trust = subLists.eoe[instance].eoesubtrust / total;
+ if(subLists.eoe[instance].trust > 100) { subLists.eoe[instance].trust = 100; }
+ if(subLists.eoe[instance].trust < -100) { subLists.eoe[instance].trust = -100; }
+ }
+ }
+ addTable("eoe", "Enemies of Enemies", subLists.eoe);
+ document.getElementById("enemiesOfEnemies").disabled = false;
+}
+
+// (Re-)render a list to the page
+function addTable(id, title, data) {
+ var mainDiv = document.getElementById("hosts"+id);
+
+ // Additional tables can be added in any order/combination
+ // So we don't want to delete if they're already there
+ // Because then we'd have to remember the order...
+ // Instead we only delete the list itself
+ // (But this function also adds those additional tables
+ // so it can't assume the table is there either)
+ if(mainDiv != null) {
+ var listElem = document.getElementById("hosts"+id+"List");
+ } else {
+ // Build the element if it didn't already exist
+ mainDiv = document.createElement("div");
+ mainDiv.setAttribute("id", "hosts"+id);
+ mainDiv.setAttribute("class", "subpanel");
+
+ var button = document.createElement("button");
+ button.setAttribute("id", "hosts"+id+"Open");
+ button.setAttribute("class", "panelButton");
+ button.innerHTML = "+";
+ button.onclick = openPanel;
+ mainDiv.appendChild(button);
+
+ var header = document.createElement("h2");
+ header.innerHTML = title;
+ mainDiv.appendChild(header);
+
+ var listElem = document.createElement("div");
+ listElem.setAttribute("id", "hosts"+id+"List");
+ listElem.setAttribute("class", "list");
+ listElem.setAttribute("listId", id);
+
+ mainDiv.appendChild(listElem);
+ document.getElementById("hostsPanel").appendChild(mainDiv);
+ }
+ // Resets the list element
+ listElem.innerHTML =
+ "<div><h3 onclick='setSort(event);'>Instance</h3>" +
+ "<div class='comments'></div>"+
+ "<div onclick='setSort(event);'><h3>Trust Value</h3></div></div>";
+
+ // Note that this is where it is doing the actual sorting of the lists
+ // Then it creates an entry for each in the list
+ var keys = Object.keys(data).sort(sortFuncArr["hosts"+id+"List"].function);
+ for(var i = 0; i < keys.length; i++) {
+ var instance = keys[i];
+
+ var instElem = document.createElement("div");
+ instElem.innerHTML = "<h3>" + instance + "</h3>";
+ instElem.setAttribute("id", "instance-"+instance);
+ instElem.setAttribute("class", "instanceElem");
+
+ var commentsElem = document.createElement("div");
+ commentsElem.setAttribute("class", "comments");
+ commentsElem.innerHTML = "";
+ for(inst in data[instance].reasons) {
+ commentsElem.innerHTML += inst + " - " + data[instance].reasons[inst] + "<br>";
+ }
+ instElem.appendChild(commentsElem);
+
+ var divElem = document.createElement("div");
+ var rangeElem = document.createElement("input");
+ rangeElem.setAttribute("type", "range");
+ rangeElem.setAttribute("min", "-100");
+ rangeElem.setAttribute("max", "100");
+ rangeElem.setAttribute("value",data[instance].trust);
+ rangeElem.oninput = syncRange;
+ rangeElem.onchange = updateRange;
+ divElem.appendChild(rangeElem);
+
+ var valElem = document.createElement("input");
+ valElem.setAttribute("type", "text");
+ valElem.value = data[instance].trust;
+ valElem.onchange = updateVal;
+ divElem.appendChild(valElem);
+ instElem.appendChild(divElem);
+
+ // We only have the "Add" button on the additional lists
+ if(id != "Known" && id != "Trusted" && id != "Untrusted") {
+ addButton = document.createElement("button");
+ addButton.onclick = subListAdd;
+ addButton.innerHTML = "Add";
+ instElem.appendChild(addButton);
+ }
+
+ listElem.appendChild(instElem);
+ }
+}
+
+// Handles the 'Add' button on additional lists
+function subListAdd(event) {
+ var instance = event.target.parentElement.children[0].innerText;
+ var id = event.target.parentElement.parentElement.getAttribute("listId");
+
+ // TODO: Check for confirmation if it already exists
+ instances[instance] = subLists[id][instance];
+ event.target.parentElement.parentElement.removeChild(event.target.parentElement);
+ resetHosts();
+}
+
+// Handles clicking on the column header to sort
+function setSort(event) {
+ var value = event.target.innerText;
+ // The element clicked could be at two different depths...
+ // I should probably just make that consistent...
+ var list = event.target.parentElement.parentElement.getAttribute("id");
+ if(list == null) {
+ list = event.target.parentElement.parentElement.parentElement.getAttribute("id");
+ }
+ // If they clicked 'Instance', sort by domain
+ if( value == 'Instance' ) {
+ sortFuncArr[list].direction = sortFuncArr[list].direction * -1;
+ sortFuncArr[list].function = function(a,b) {
+ return sortDomains(sortFuncArr[list].direction,a,b);};
+ // If they clicked 'Trust Value', sort by that
+ } else if( value == 'Trust Value' ) {
+ sortFuncArr[list].direction = sortFuncArr[list].direction * -1;
+ // Main three lists are in 'instances', others are in subLists
+ if( list == "hostsKnownList" || list == "hostsTrustedList" || list == "hostsUntrustedList" ) {
+ sortFuncArr[list].function = function(a,b) {
+ return sortTrust(sortFuncArr[list].direction,instances,a,b);};
+ } else {
+ // list will be an element id, like 'hostseoeList', so slice takes off 'hosts' and 'List'
+ sortFuncArr[list].function = function(a,b) {
+ return sortTrust(sortFuncArr[list].direction,subLists[list.slice(5).slice(0,-4)],a,b);};
+ }
+ }
+ resetHosts();
+}
+
+// Trigger a re-render of all lists
+function resetHosts() {
+ var trusted = [];
+ var untrusted = [];
+ var known = [];
+ for(instance in instances) {
+ if(instances[instance].trust > 0) {
+ trusted[instance] = instances[instance];
+ } else if(instances[instance].trust < 0) {
+ untrusted[instance] = instances[instance];
+ } else {
+ known[instance] = instances[instance];
+ }
+ }
+ addTable("Trusted", "Trusted Hosts", trusted);
+ addTable("Untrusted", "Untrusted Hosts", untrusted);
+ addTable("Known", "Known Hosts", known);
+ var lists = Object.keys(subLists);
+ for(var i = 0; i < lists.length; i++) {
+ list = lists[i];
+ addTable(list, list, subLists[list]);
+ }
+}
+
+// Toggle a panel open or closed by showing or hiding the list element
+function openPanel(event) {
+ var listElem = this.parentElement.getElementsByClassName("list")[0];
+ if( listElem.style.display == "block" ) {
+ this.parentElement.getElementsByClassName("list")[0].style.display = "none";
+ this.innerHTML = "+";
+ } else {
+ this.parentElement.getElementsByClassName("list")[0].style.display = "block";
+ this.innerHTML = "-";
+ }
+}
+
+// Update the instance's range slider from a value entered in the text box
+// (Also update the element's trust value, and move to the appropriate list if necessary)
+function updateRange(event) {
+ var id = this.parentElement.parentElement.parentElement.getAttribute("listId");
+ var instance = this.parentElement.parentElement.firstChild.innerText;
+ if( id == null) {
+ instances[instance].trust = this.value;
+ } else {
+ subLists[id][instance].trust = this.value;
+ }
+ this.parentElement.children[1].value = this.value;
+
+ if(id == null) {
+ var parentPanel = this.parentElement.parentElement.parentElement;
+ var instElem = this.parentElement.parentElement;
+ if( this.value < 0 && parentPanel != document.getElementById("hostsUntrustedList") ) {
+ parentPanel.removeChild(instElem);
+ document.getElementById("hostsUntrustedList").appendChild(instElem);
+ } else if( this.value > 0 && parentPanel != document.getElementById("hostsTrustedList") ) {
+ parentPanel.removeChild(instElem);
+ document.getElementById("hostsTrustedList").appendChild(instElem);
+ } else if( this.value == 0 && parentPanel != document.getElementById("hostsKnownList") ) {
+ parentPanel.removeChild(instElem);
+ document.getElementById("hostsKnownList").appendChild(instElem);
+ }
+ }
+}
+
+// Update the instance's text box each time the range slider is moved
+function syncRange(event) {
+ this.parentElement.children[1].value = this.value;
+}
+
+// Update the instance's text box from a value entered in the range slider
+// (Also update the element's trust value, and move to the appropriate list if necessary)
+function updateVal(event) {
+ var id = event.target.parentElement.parentElement.getAttribute("listId");
+ var instance = this.parentElement.parentElement.firstChild.innerText;
+ if(this.value < -100) { this.value = -100; }
+ if(this.value > 100) { this.value = 100; }
+ this.parentElement.children[0].value = this.value;
+ instances[instance].trust = this.value;
+
+ // The initial three lists don't have this id element
+ // If it's not on those lists it shouldn't move when the trust value is changed
+ if(id == null) {
+ var parentPanel = this.parentElement.parentElement.parentElement;
+ var instElem = this.parentElement.parentElement;
+ if( this.value < 0 && parentPanel != document.getElementById("hostsUntrustedList") ) {
+ parentPanel.removeChild(instElem);
+ document.getElementById("hostsUntrustedList").appendChild(instElem);
+ } else if( this.value > 0 && parentPanel != document.getElementById("hostsTrustedList") ) {
+ parentPanel.removeChild(instElem);
+ document.getElementById("hostsTrustedList").appendChild(instElem);
+ } else if( this.value == 0 && parentPanel != document.getElementById("hostsKnownList") ) {
+ parentPanel.removeChild(instElem);
+ document.getElementById("hostsKnownList").appendChild(instElem);
+ }
+ }
+}
+
+// Draw the pop-up to import or export from a string
+function importStr(str = "") {
+ var shade = document.createElement("div");
+ shade.setAttribute("id", "shade");
+ shade.onclick = function() {
+ document.body.removeChild(document.getElementById("shade"));
+ document.body.removeChild(document.getElementById("shade-text"));
+ document.body.removeChild(document.getElementById("shade-text-button"));
+ }
+
+ var text = document.createElement("textarea");
+ text.innerHTML = str;
+ text.setAttribute("id", "shade-text");
+
+ var btn = document.createElement("button");
+ btn.innerHTML = "Import";
+ btn.setAttribute("id", "shade-text-button");
+ btn.onclick = function() {
+ loadStr(document.getElementById("shade-text").value);
+ document.getElementById("shade").onclick();
+ resetHosts();
+ };
+
+ document.body.appendChild(shade);
+ document.body.appendChild(text);
+ document.body.appendChild(btn);
+}
+
+// Leverage the import code to display an export string
+function exportStr() {
+ importStr(saveStr());
+}
+
+// Load from the browser cache
+function loadCache() {
+ loadStr(localStorage.getItem("config"));
+ resetHosts();
+}
+
+// Save to the browser cache
+function saveCache() {
+ localStorage.setItem("config", saveStr());
+}
+
+// Start ten threads checking and hiding impotent domains
+function hideImpotent() {
+ var keys = Object.keys(instances);
+ for(i = 0; i < keys.length; i+=(keys.length/10)) {
+ var sublist = keys.slice(i, i+(keys.length/10));
+ var instance = sublist.pop();
+ checkImpotence( sublist, instance )
+ }
+}
+
+// Hide any instances that are a subdomain of another known instance
+async function hideSubdomains() {
+ for(instance in instances) {
+ var domainParts = instance.split('.');
+ for(var i = 1; i < domainParts.length-1; i++) {
+ var parent = domainParts.slice(i).join('.');
+ if(typeof instances[parent] != undefined) {
+ instances[instance].parent = parent;
+ var instElem = document.getElementById("instance-"+instance);
+ if(instElem != null) {
+ instElem.style.display = "none";
+ }
+ break;
+ }
+ }
+ }
+}
+
+// Export a CSV to be used with the given import script
+function exportCSV() {
+ var data = "";
+ for(instance in instances) {
+ var action = "";
+ if(instances[instance].trust <= document.getElementById('threshold-limit').value) {
+ action = "limit";
+ } else if(instances[instance].trust <= document.getElementById('threshold-suspend').value) {
+ action = "suspend";
+ }
+
+ if(action != "") {
+ data += instance + ",";
+ data += action + ",";
+ data += '"';
+ for(inst in instances[instance].reasons) {
+ data += inst + " - " + instances[instance].reasons[inst] + "\n";
+ }
+ data = data.trim();
+ data += '"\n';
+ }
+ }
+ var elem = document.createElement("a");
+ elem.setAttribute("href", "data:text/plain;charset=utf-8,"
+ + encodeURIComponent(data.trim()));
+ elem.setAttribute('download', 'mastowot.csv');
+ document.body.appendChild(elem);
+ elem.click();
+ document.body.removeChild(elem);
+}
+
+// @license-end