serverping.js 10 KB

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