serverping.js 11 KB

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