serverping.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. function htmlspecialchars(str){
  2. if(str === null){
  3. return "<i>&lt;Empty&gt;</i>";
  4. }
  5. var map = {
  6. '&': '&amp;',
  7. '<': '&lt;',
  8. '>': '&gt;',
  9. '"': '&quot;',
  10. "'": '&#039;'
  11. }
  12. return str.replace(/[&<>"']/g, function(m){return map[m];});
  13. }
  14. // initialize garbage
  15. var list = [];
  16. var pinged_list = [];
  17. var reqs = 0;
  18. var errors = 0;
  19. var sort = 6; // highest version first
  20. // check for instance redirect stuff
  21. var redir = [];
  22. var target = "/web?";
  23. new URL(window.location.href)
  24. .searchParams
  25. .forEach(
  26. function(value, key){
  27. if(key == "target"){
  28. target = "/" + encodeURIComponent(value) + "?";
  29. return;
  30. }
  31. if(key == "npt"){ return; }
  32. redir.push(encodeURIComponent(key) + "=" + encodeURIComponent(value))
  33. }
  34. );
  35. if(redir.length !== 0){
  36. redir = target + redir.join("&");
  37. }else{
  38. redir = "";
  39. }
  40. var quote = document.createElement("div");
  41. quote.className = "quote";
  42. quote.innerHTML = 'Pinged <b>0</b> servers (<b>0</b> failed requests)';
  43. var [div_servercount, div_failedreqs] =
  44. quote.getElementsByTagName("b");
  45. var noscript = document.getElementsByTagName("noscript")[0];
  46. document.body.insertBefore(quote, noscript.nextSibling);
  47. // create table
  48. var table = document.createElement("table");
  49. table.innerHTML =
  50. '<thead>' +
  51. '<tr>' +
  52. '<th class="extend">Server</th>' +
  53. '<th>Address</th>' +
  54. '<th>Bot protection</th>' +
  55. '<th title="Amount of legit requests processed since the last APCU cache clear (usually happens at midnight)">Real reqs (?)</th>' +
  56. '<th title="Amount of filtered requests processed since the last APCU cache clear (usually happens at midnight)">Bot reqs (?)</th>' +
  57. '<th>API</th>' +
  58. '<th><div class="arrow up"></div>Version</th>' +
  59. '</tr>' +
  60. '</thead>' +
  61. '<tbody></tbody>';
  62. document.body.insertBefore(table, quote.nextSibling);
  63. // handle sorting clicks
  64. var tbody = table.getElementsByTagName("tbody")[0];
  65. var th = table.getElementsByTagName("th");
  66. for(var i=0; i<th.length; i++){
  67. th[i].addEventListener("click", function(event){
  68. if(event.target.className.includes("arrow")){
  69. var div = event.target.parentElement;
  70. }else{
  71. var div = event.target;
  72. }
  73. var arrow = div.getElementsByClassName("arrow");
  74. var orientation = 0; // up
  75. if(arrow.length === 0){
  76. // delete arrow and add new one
  77. arrow = document.getElementsByClassName("arrow");
  78. arrow[0].remove();
  79. arrow = document.createElement("div");
  80. arrow.className = "arrow up";
  81. div.insertBefore(arrow, event.target.firstChild);
  82. }else{
  83. // switch arrow position
  84. if(arrow[0].className == "arrow down"){
  85. arrow[0].className = "arrow up";
  86. }else{
  87. arrow[0].className = "arrow down";
  88. orientation = 1;
  89. }
  90. }
  91. switch(div.textContent.toLowerCase()){
  92. case "server": sort = 0 + orientation; break;
  93. case "address": sort = 2 + orientation; break;
  94. case "bot protection": sort = 4 + orientation; break;
  95. case "real reqs (?)": sort = 6 + orientation; break;
  96. case "bot reqs (?)": sort = 8 + orientation; break;
  97. case "api": sort = 10 + orientation; break;
  98. case "version": sort = 12 + orientation; break;
  99. }
  100. render_list();
  101. });
  102. }
  103. function validate_url(url, allow_http = false){
  104. try{
  105. url = new URL(url);
  106. if(
  107. url.protocol == "https:" ||
  108. (
  109. (
  110. allow_http === true ||
  111. window.location.protocol == "http:"
  112. ) &&
  113. url.protocol == "http:"
  114. )
  115. ){
  116. return true;
  117. }
  118. }catch(error){} // do nothing
  119. return false;
  120. }
  121. function number_format(int){
  122. return new Intl.NumberFormat().format(int);
  123. }
  124. // parse initial server list
  125. fetch_server(window.location.origin);
  126. async function fetch_server(server){
  127. if(!validate_url(server)){
  128. console.warn("Invalid server URL: " + server);
  129. return;
  130. }
  131. // make sure baseURL is origin
  132. server = new URL(server).origin;
  133. // prevent multiple fetches
  134. for(var i=0; i<list.length; i++){
  135. if(list[i] == server){
  136. // serber was already fetched
  137. return;
  138. }
  139. }
  140. // prevent future fetches
  141. list.push(server);
  142. var data = null;
  143. try{
  144. var payload = await fetch(server + "/ami4get");
  145. if(payload.status !== 200){
  146. // endpoint is not available
  147. errors++;
  148. div_failedreqs.textContent = number_format(errors);
  149. console.warn(server + ": Invalid HTTP code " + payload.status);
  150. return;
  151. }
  152. data = await payload.json();
  153. }catch(error){
  154. errors++;
  155. div_failedreqs.textContent = number_format(errors);
  156. console.warn(server + ": Could not fetch or decode JSON");
  157. return;
  158. }
  159. // sanitize data
  160. if(
  161. typeof data.status != "string" ||
  162. data.status != "ok" ||
  163. typeof data.server != "object" ||
  164. !(
  165. typeof data.server.name == "string" ||
  166. (
  167. typeof data.server.name == "object" &&
  168. data.server.name === null
  169. )
  170. ) ||
  171. typeof data.service != "string" ||
  172. data.service != "4get" ||
  173. (
  174. typeof data.server.description != "string" &&
  175. data.server.description !== null
  176. ) ||
  177. typeof data.server.bot_protection != "number" ||
  178. typeof data.server.real_requests != "number" ||
  179. typeof data.server.bot_requests != "number" ||
  180. typeof data.server.api_enabled != "boolean" ||
  181. typeof data.server.alt_addresses != "object" ||
  182. typeof data.server.version != "number" ||
  183. typeof data.instances != "object"
  184. ){
  185. errors++;
  186. div_failedreqs.textContent = number_format(errors);
  187. console.warn(server + ": Malformed JSON");
  188. return;
  189. }
  190. data.server.ip = server;
  191. reqs++;
  192. div_servercount.textContent = number_format(reqs);
  193. var total = pinged_list.push(data) - 1;
  194. pinged_list[total].index = total;
  195. render_list();
  196. // get more serbers
  197. for(var i=0; i<data.instances.length; i++){
  198. fetch_server(data.instances[i]);
  199. }
  200. }
  201. function sorta(object, element, order){
  202. return object.slice().sort(
  203. function(a, b){
  204. if(order){
  205. return a.server[element] - b.server[element];
  206. }
  207. return b.server[element] - a.server[element];
  208. }
  209. );
  210. }
  211. function textsort(object, element, order){
  212. var sort = object.slice().sort(
  213. function(a, b){
  214. return a.server[element].localeCompare(b.server[element]);
  215. }
  216. );
  217. if(!order){
  218. return sort.reverse();
  219. }
  220. return sort;
  221. }
  222. function render_list(){
  223. var sorted_list = [];
  224. // sort
  225. var filter = Boolean(sort % 2);
  226. switch(sort){
  227. case 0:
  228. case 1:
  229. sorted_list = textsort(pinged_list, "name", filter === true ? false : true);
  230. break;
  231. case 2:
  232. case 3:
  233. sorted_list = textsort(pinged_list, "ip", filter === true ? false : true);
  234. break;
  235. case 4:
  236. case 5:
  237. sorted_list = sorta(pinged_list, "bot_protection", filter === true ? false : true);
  238. break;
  239. case 6:
  240. case 7:
  241. sorted_list = sorta(pinged_list, "real_requests", filter);
  242. break;
  243. case 8:
  244. case 9:
  245. sorted_list = sorta(pinged_list, "bot_requests", filter);
  246. break;
  247. case 10:
  248. case 11:
  249. sorted_list = sorta(pinged_list, "api_enabled", filter);
  250. break;
  251. case 12:
  252. case 13:
  253. sorted_list = sorta(pinged_list, "version", filter);
  254. break;
  255. }
  256. // render tabloid
  257. var html = "";
  258. for(var k=0; k<sorted_list.length; k++){
  259. html += '<tr onclick="show_server(' + sorted_list[k].index + ');">';
  260. for(var i=0; i<7; i++){
  261. html += '<td';
  262. switch(i){
  263. // server name
  264. case 0: html += ' class="extend">' + htmlspecialchars(sorted_list[k].server.name); break;
  265. case 1: html += '>' + htmlspecialchars(new URL(sorted_list[k].server.ip).host); break;
  266. case 2: // bot protection
  267. switch(sorted_list[k].server.bot_protection){
  268. case 0:
  269. html += '><span style="color:var(--green);">Disabled</span>';
  270. break;
  271. case 1:
  272. html += '><span style="color:var(--yellow);">Image captcha</span>';
  273. break;
  274. case 2:
  275. html += '><span style="color:var(--red);">Invite only</span>';
  276. break;
  277. default:
  278. html += '>Unknown';
  279. }
  280. break;
  281. case 3: // real reqs
  282. html += '>' + number_format(sorted_list[k].server.real_requests);
  283. break;
  284. case 4: // bot reqs
  285. html += '>' + number_format(sorted_list[k].server.bot_requests);
  286. break;
  287. case 5: // api enabled
  288. if(sorted_list[k].server.api_enabled){
  289. html += '><span style="color:var(--green);">Yes</span>';
  290. }else{
  291. html += '><span style="color:var(--red);">No</span>';
  292. }
  293. break;
  294. // version
  295. case 6: html += ">v" + sorted_list[k].server.version; break;
  296. }
  297. html += '</td>';
  298. }
  299. html += '</tr>';
  300. }
  301. console.log(html);
  302. tbody.innerHTML = html;
  303. }
  304. var popup_bg = document.getElementById("popup-bg");
  305. var popup_wrapper = document.getElementsByClassName("popup-wrapper")[0];
  306. var popup = popup_wrapper.getElementsByClassName("popup")[0];
  307. var popup_shown = false;
  308. popup_bg.addEventListener("click", function(){
  309. popup_wrapper.style.display = "none";
  310. popup_bg.style.display = "none";
  311. });
  312. function show_server(serverid){
  313. var html =
  314. '<h2>' + htmlspecialchars(pinged_list[serverid].server.name) + '</h2>' +
  315. 'Description' +
  316. '<div class="code">' + htmlspecialchars(pinged_list[serverid].server.description) + '</div>';
  317. var url_obj = new URL(pinged_list[serverid].server.ip);
  318. var url = htmlspecialchars(url_obj.origin);
  319. var domain = url_obj.hostname;
  320. html +=
  321. '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>' +
  322. '<br><br>Alt addresses:';
  323. var len = pinged_list[serverid].server.alt_addresses.length;
  324. if(len === 0){
  325. html += ' <i>&lt;Empty&gt;</i>';
  326. }else{
  327. html += '<ul>';
  328. for(var i=0; i<len; i++){
  329. var url_obj = new URL(pinged_list[serverid].server.alt_addresses[i]);
  330. var url = htmlspecialchars(url_obj.origin);
  331. var domain = url_obj.hostname;
  332. if(validate_url(pinged_list[serverid].server.alt_addresses[i], true)){
  333. 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>';
  334. }else{
  335. console.warn(pinged_list[serverid].server.ip + ": Invalid peer URL => " + pinged_list[serverid].server.alt_addresses[i]);
  336. }
  337. }
  338. html += '</ul>';
  339. }
  340. popup.innerHTML = html;
  341. popup_wrapper.style.display = "block";
  342. popup_bg.style.display = "block";
  343. }
  344. function hide_server(){
  345. popup_wrapper.style.display = "none";
  346. popup_bg.style.display = "none";
  347. }