summaryrefslogtreecommitdiff
path: root/static/serverping.js
diff options
context:
space:
mode:
Diffstat (limited to 'static/serverping.js')
-rw-r--r--static/serverping.js495
1 files changed, 495 insertions, 0 deletions
diff --git a/static/serverping.js b/static/serverping.js
new file mode 100644
index 0000000..5fe285d
--- /dev/null
+++ b/static/serverping.js
@@ -0,0 +1,495 @@
+
+function htmlspecialchars(str){
+
+ if(str === null){
+
+ return "<i>&lt;Empty&gt;</i>";
+ }
+
+ var map = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&quot;',
+ "'": '&#039;'
+ }
+
+ return str.replace(/[&<>"']/g, function(m){return map[m];});
+}
+
+// initialize garbage
+var list = [];
+var pinged_list = [];
+var reqs = 0;
+var errors = 0;
+var sort = 0; // lower ping first
+
+// check for instance redirect stuff
+var redir = "";
+var target = "/web?";
+new URL(window.location.href)
+ .searchParams
+ .forEach(
+ function(value, key){
+
+ if(key == "target"){
+
+ target = "/" + encodeURIComponent(value) + "?";
+ return;
+ }
+
+ if(key == "npt"){ return; }
+ redir += encodeURIComponent(key) + "=" + encodeURIComponent(value)
+ }
+ );
+
+if(redir != ""){
+ redir = target + redir;
+}
+
+var quote = document.createElement("div");
+quote.className = "quote";
+quote.innerHTML = 'Pinged <b>0</b> servers (<b>0</b> failed requests)';
+var [div_servercount, div_failedreqs] =
+ quote.getElementsByTagName("b");
+
+var noscript = document.getElementsByTagName("noscript")[0];
+document.body.insertBefore(quote, noscript.nextSibling);
+
+// create table
+var table = document.createElement("table");
+table.innerHTML =
+ '<thead>' +
+ '<tr>' +
+ '<th><div class="arrow up"></div>Ping</th>' +
+ '<th class="extend">Server</th>' +
+ '<th>Address</th>' +
+ '<th>Bot protection</th>' +
+ '<th title="Amount of legit requests processed since the last APCU cache clear (usually happens at midnight)">Real reqs (?)</th>' +
+ '<th title="Amount of filtered requests processed since the last APCU cache clear (usually happens at midnight)">Bot reqs (?)</th>' +
+ '<th>API</th>' +
+ '<th>Version</th>' +
+ '</tr>' +
+ '</thead>' +
+ '<tbody></tbody>';
+
+document.body.insertBefore(table, quote.nextSibling);
+
+// handle sorting clicks
+var tbody = table.getElementsByTagName("tbody")[0];
+var th = table.getElementsByTagName("th");
+
+for(var i=0; i<th.length; i++){
+
+ th[i].addEventListener("click", function(event){
+
+ if(event.target.className.includes("arrow")){
+
+ var div = event.target.parentElement;
+ }else{
+
+ var div = event.target;
+ }
+
+ var arrow = div.getElementsByClassName("arrow");
+ var orientation = 0; // up
+
+ if(arrow.length === 0){
+
+ // delete arrow and add new one
+ arrow = document.getElementsByClassName("arrow");
+ arrow[0].remove();
+
+ arrow = document.createElement("div");
+ arrow.className = "arrow up";
+ div.insertBefore(arrow, event.target.firstChild);
+ }else{
+
+ // switch arrow position
+ if(arrow[0].className == "arrow down"){
+
+ arrow[0].className = "arrow up";
+ }else{
+
+ arrow[0].className = "arrow down";
+ orientation = 1;
+ }
+ }
+
+ switch(div.textContent.toLowerCase()){
+
+ case "ping": sort = orientation; break;
+ case "server": sort = 2 + orientation; break;
+ case "address": sort = 4 + orientation; break;
+ case "bot protection": sort = 6 + orientation; break;
+ case "real reqs (?)": sort = 8 + orientation; break;
+ case "bot reqs (?)": sort = 10 + orientation; break;
+ case "api": sort = 12 + orientation; break;
+ case "version": sort = 14 + orientation; break;
+ }
+
+ render_list();
+ });
+}
+
+function validate_url(url, allow_http = false){
+
+ try{
+
+ url = new URL(url);
+ if(
+ url.protocol == "https:" ||
+ (
+ (
+ allow_http === true ||
+ window.location.protocol == "http:"
+ ) &&
+ url.protocol == "http:"
+ )
+ ){
+
+ return true;
+ }
+ }catch(error){} // do nothing
+
+ return false;
+}
+
+function number_format(int){
+
+ return new Intl.NumberFormat().format(int);
+}
+
+// parse initial server list
+fetch_server(window.location.origin);
+
+async function fetch_server(server){
+
+ if(!validate_url(server)){
+ console.warn("Invalid server URL: " + server);
+ return;
+ }
+
+ // make sure baseURL is origin
+ server = new URL(server).origin;
+ // prevent multiple fetches
+ for(var i=0; i<list.length; i++){
+
+ if(list[i] == server){
+
+ // serber was already fetched
+ console.info("Already checked server: " + server);
+ return;
+ }
+ }
+
+ // prevent future fetches
+ list.push(server);
+
+ var data = null;
+ var ping = new Date().getTime();
+
+ try{
+
+ data = await fetch(
+ server + "/ami4get"
+ );
+
+ if(data.status !== 200){
+
+ // endpoint is not available
+ errors++;
+ div_failedreqs.textContent = number_format(errors);
+ console.warn(server + ": Invalid HTTP code " + data.status);
+ return;
+ }
+
+ data = await data.json();
+ data.server.ping = new Date().getTime() - ping;
+
+ }catch(error){
+
+ errors++;
+ div_failedreqs.textContent = number_format(errors);
+ console.warn(server + ": Could not fetch or decode JSON");
+ return;
+ }
+
+ // sanitize data
+ if(
+ typeof data.status != "string" ||
+ data.status != "ok" ||
+ typeof data.server != "object" ||
+ !(
+ typeof data.server.name == "string" ||
+ (
+ typeof data.server.name == "object" &&
+ data.server.name === null
+ )
+ ) ||
+ typeof data.service != "string" ||
+ data.service != "4get" ||
+ (
+ typeof data.server.description != "string" &&
+ data.server.description !== null
+ ) ||
+ typeof data.server.bot_protection != "number" ||
+ typeof data.server.real_requests != "number" ||
+ typeof data.server.bot_requests != "number" ||
+ typeof data.server.api_enabled != "boolean" ||
+ typeof data.server.alt_addresses != "object" ||
+ typeof data.server.version != "number" ||
+ typeof data.instances != "object"
+ ){
+
+ errors++;
+ div_failedreqs.textContent = number_format(errors);
+ console.warn(server + ": Malformed JSON");
+ return;
+ }
+
+ data.server.ip = server;
+
+ reqs++;
+ div_servercount.textContent = number_format(reqs);
+
+ var total = pinged_list.push(data) - 1;
+ pinged_list[total].index = total;
+
+ render_list();
+
+ // get more serbers
+ for(var i=0; i<data.instances.length; i++){
+
+ fetch_server(data.instances[i]);
+ }
+}
+
+function sorta(object, element, order){
+
+ return object.slice().sort(
+ function(a, b){
+
+ if(order){
+
+ return a.server[element] - b.server[element];
+ }
+
+ return b.server[element] - a.server[element];
+ }
+ );
+}
+
+function textsort(object, element, order){
+
+ var sort = object.slice().sort(
+ function(a, b){
+
+ return a.server[element].localeCompare(b.server[element]);
+ }
+ );
+
+ if(!order){
+ return sort.reverse();
+ }
+
+ return sort;
+}
+
+function render_list(){
+
+ var sorted_list = [];
+
+ // sort
+ var filter = Boolean(sort % 2);
+
+ switch(sort){
+
+ case 0:
+ case 1:
+ sorted_list = sorta(pinged_list, "ping", filter === true ? false : true);
+ break;
+
+ case 2:
+ case 3:
+ sorted_list = textsort(pinged_list, "name", filter === true ? false : true);
+ break;
+
+ case 4:
+ case 5:
+ sorted_list = textsort(pinged_list, "ip", filter === true ? false : true);
+ break;
+
+ case 6:
+ case 7:
+ sorted_list = sorta(pinged_list, "bot_protection", filter === true ? false : true);
+ break;
+
+ case 8:
+ case 9:
+ sorted_list = sorta(pinged_list, "real_requests", filter);
+ break;
+
+ case 10:
+ case 11:
+ sorted_list = sorta(pinged_list, "bot_requests", filter);
+ break;
+
+ case 12:
+ case 13:
+ sorted_list = sorta(pinged_list, "api_enabled", filter);
+ break;
+
+ case 14:
+ case 15:
+ sorted_list = sorta(pinged_list, "version", filter);
+ break;
+ }
+
+ // render tabloid
+ var html = "";
+
+ for(var k=0; k<sorted_list.length; k++){
+
+ html += '<tr onclick="show_server(' + sorted_list[k].index + ');">';
+
+ for(var i=0; i<8; i++){
+
+ html += '<td';
+
+ switch(i){
+
+ case 0: // server ping
+ if(sorted_list[k].server.ping <= 100){
+
+ html += '><span style="color:var(--green);">' + sorted_list[k].server.ping + '</span>';
+ break;
+ }
+
+ if(sorted_list[k].server.ping <= 200){
+
+ html += '><span style="color:var(--yellow);">' + sorted_list[k].server.ping + '</span>';
+ break;
+ }
+
+ html += '><span style="color:var(--red);">' + number_format(sorted_list[k].server.ping) + '</span>';
+ break;
+
+ // server name
+ case 1: html += ' class="extend">' + htmlspecialchars(sorted_list[k].server.name); break;
+ case 2: html += '>' + htmlspecialchars(new URL(sorted_list[k].server.ip).host); break;
+ case 3: // bot protection
+ switch(sorted_list[k].server.bot_protection){
+
+ case 0:
+ html += '><span style="color:var(--green);">Disabled</span>';
+ break;
+
+ case 1:
+ html += '><span style="color:var(--yellow);">Image captcha</span>';
+ break;
+
+ case 2:
+ html += '><span style="color:var(--red);">Invite only</span>';
+ break;
+
+ default:
+ html += '>Unknown';
+ }
+ break;
+
+ case 4: // real reqs
+ html += '>' + number_format(sorted_list[k].server.real_requests);
+ break;
+
+ case 5: // bot reqs
+ html += '>' + number_format(sorted_list[k].server.bot_requests);
+ break;
+
+ case 6: // api enabled
+
+ if(sorted_list[k].server.api_enabled){
+
+ html += '><span style="color:var(--green);">Yes</span>';
+ }else{
+
+ html += '><span style="color:var(--red);">No</span>';
+ }
+ break;
+
+ // version
+ case 7: html += ">v" + sorted_list[k].server.version; break;
+ }
+
+ html += '</td>';
+ }
+
+ html += '</tr>';
+ }
+
+ tbody.innerHTML = html;
+}
+
+var popup_bg = document.getElementById("popup-bg");
+var popup_wrapper = document.getElementsByClassName("popup-wrapper")[0];
+var popup = popup_wrapper.getElementsByClassName("popup")[0];
+var popup_shown = false;
+
+popup_bg.addEventListener("click", function(){
+
+ popup_wrapper.style.display = "none";
+ popup_bg.style.display = "none";
+});
+
+function show_server(serverid){
+
+ var html =
+ '<h2>' + htmlspecialchars(pinged_list[serverid].server.name) + '</h2>' +
+ 'Description' +
+ '<div class="code">' + htmlspecialchars(pinged_list[serverid].server.description) + '</div>';
+
+ var url_obj = new URL(pinged_list[serverid].server.ip);
+ var url = htmlspecialchars(url_obj.origin);
+ var domain = url_obj.hostname;
+
+ html +=
+ 'URL: <a rel="noreferer" target="_BLANK" href="' + url + redir + '">' + url + '</a> <a rel="noreferer" target="_BLANK" href="https://browserleaks.com/ip/' + encodeURIComponent(domain) + '">(IP lookup)</a>' +
+ '<br><br>Alt addresses:';
+
+ var len = pinged_list[serverid].server.alt_addresses.length;
+
+ if(len === 0){
+
+ html += ' <i>&lt;Empty&gt;</i>';
+ }else{
+
+ html += '<ul>';
+
+ for(var i=0; i<len; i++){
+
+ var url_obj = new URL(pinged_list[serverid].server.alt_addresses[i]);
+ var url = htmlspecialchars(url_obj.origin);
+ var domain = url_obj.hostname;
+
+ if(validate_url(pinged_list[serverid].server.alt_addresses[i], true)){
+
+ html += '<li><a rel="noreferer" href="' + url + redir + '" target="_BLANK">' + url + '</a> <a rel="noreferer" target="_BLANK" href="https://browserleaks.com/ip/' + encodeURIComponent(domain) + '">(IP lookup)</a></li>';
+ }else{
+
+ console.warn(pinged_list[serverid].server.ip + ": Invalid peer URL => " + pinged_list[serverid].server.alt_addresses[i]);
+ }
+ }
+
+ html += '</ul>';
+ }
+ popup.innerHTML = html;
+
+ popup_wrapper.style.display = "block";
+ popup_bg.style.display = "block";
+}
+
+function hide_server(){
+
+ popup_wrapper.style.display = "none";
+ popup_bg.style.display = "none";
+}