function prepend(elem, children) { var child = elem.firstChild; if (child) { elem.insertBefore(children, child); } else { elem.appendChild(children); } } function _log() { for (var arg of arguments) { var pre = document.createElement('pre'); pre.appendChild(text(arg.toString())); document.body.appendChild(pre); try { prepend(document.body, pre); } catch (e) { var pre = document.createElement('pre'); pre.appendChild(text(e.toString())); document.body.appendChild(pre); } } } /* var console = { log: _log, error: _log }; */ var CurrentScore = null; var global_elements; var GlobalElemSelectors = [ 'style' , 'style.loading' , '.info' ] var pageInfoText; var GlobalCancellableEvents = []; var cancel = GlobalCancellableEvents.push.bind(GlobalCancellableEvents); /** * Router */ // [ fpart : next ], content function RoutePart(fparts, content) { return { fparts, content } } function iter(iarray) { var i = 0; return function (f, g) { console.log(i, iarray.length, iarray); if (i < iarray.length) { console.log("calling", f, "with", iarray[i]); f(iarray[i++]); } else { console.log("calling", g); g(); } } } function resolvePath(routes, path, query_params) { console.log("resolvePath:", arguments); var next = iter(path); function goRoute(path_data, route_part, path_part) { var found = false; console.log(arguments); for (var [fpart, next_route_part] of route_part.fparts) { var parsed_part = fpart(path_part); if (parsed_part) { console.log("parsed part:", parsed_part); path_data.push(parsed_part); next( goRoute.bind(this, path_data, next_route_part), resolve.bind(this, path_data, next_route_part) ); found = true; } } if (!found) { throw new Error("404 " + path_data.join('/') + '/' + path_part); } } function resolve(path_data, route_part) { console.log('calling content'); console.log(path_data, route_part); route_part.content(path_data, query_params); } var p = []; next(goRoute.bind(this, p, routes), resolve.bind(this, p, routes)); } function match(re) { return function (s) { if (re.test(s)) { return s; } } } var ROUTES = RoutePart([ [match(/^s$/), new RoutePart([], handleSearch)], [match(/^[a-f0-9]{40}$/), new RoutePart([], handleInfo)] ], loadTop); /** * Functions */ function resolveResponse(resolve, reject) { return function(e) { var status = e.target.getResponseHeader("Status"); if (status != null) { var status_code = Number(status.split(" ")[0]); if (status_code >= 200 && status_code < 300) { resolve(e.target.responseText); } else { reject(new Error('API responded with ' + status)); } } else { if (e.target.status >= 200 && e.target.status < 300) { return resolve(e.target.responseText); } else { reject(new Error('API responded with ' + e.target.status)); } } } } function cancelGlobalEvts() { while (GlobalCancellableEvents.length) { GlobalCancellableEvents.pop().abort(); } } function get(url, xhr_req_follower) { return new Promise(function (resolve, reject) { var req = new XMLHttpRequest(); xhr_req_follower(req); req.onload = resolveResponse(resolve, reject); req.onerror = reject; req.open('GET', url); req.send(); }); } var HexArray = "0123456789abcdef"; function bytesToHex(uint8arr) { var hex_encoded = ""; for (var i=0; i < uint8arr.length; i++) { var j = uint8arr[i] & 0xFF; hex_encoded += HexArray[j >>> 4]; hex_encoded += HexArray[j & 0x0F]; } return hex_encoded; } var ESC_STR = '$ESCAPE_PIPE_SYMBOL'; function splitRawRow(raw_row) { return raw_row .replace('\\|', ESC_STR) .split('|') .map(function(s) { return s.replace(ESC_STR, '|') .trim(); }) } function parseTable(results) { var raw_rows = results.split('\n').slice(0, -1); var headers = splitRawRow(raw_rows[0]); return raw_rows.slice(2, -1) .map(splitRawRow) .map(function(row) { return row.reduce(function(acc, val, i) { acc[headers[i]] = val; return acc; }, {}); }) } function parseFilePath(s) { return JSON.parse(s); } function FileSize(s) { this.filesize = s; } function tree(rows) { var c = {}; for (var row of rows) { var path = parseFilePath(row.path); var tree = c; var previous = c; var filename = 'NOPATH'; for (var p of path) { previous = tree; if (p in tree) { tree = tree[p]; } else { var newtree = {}; tree[p] = newtree; tree = newtree; } filename = p; } previous[filename] = new FileSize(row.filesize); } console.log(c); return c; } function renderTree(rows) { var list_elem = ul(); function draw(list_elem, t) { Object.entries(t) .forEach(function([key, val]) { if (val instanceof FileSize) { var contents = div(span(text(key))); contents.appendChild(italic(text(val.filesize))); list_elem.appendChild(contents); } else { var new_list = ul(); var elem = li(text(key)); elem.appendChild(new_list); list_elem.appendChild(elem); draw(new_list, val); } }); } draw(list_elem, tree(rows)); return list_elem; } function assembleTable(rows) { var t = document.createElement('table'); rows.forEach(function(r) { var row = t.insertRow(); TableHeadings.forEach(function(header) { var cell = row.insertCell(); cell.appendChild(header[1](r)); }); }); var thead = t.createTHead().insertRow(); TableHeadingNames.forEach(function(header) { var th = document.createElement('th'); th.appendChild(text(header)); thead.appendChild(th); }); var countElem = div(text(rows.length + ' rows')) var wrapper = div(); wrapper.appendChild(t); wrapper.appendChild(countElem); return wrapper; } function text(s) { return document.createTextNode(s); } function _mkelem(etype, child) { var elem = document.createElement(etype); if (child) { elem.appendChild(child); } return elem; } var div = _mkelem.bind(this, 'div'); var li = _mkelem.bind(this, 'li'); var span = _mkelem.bind(this, 'span'); var italic = _mkelem.bind(this, 'i'); var bold = _mkelem.bind(this, 'b'); var ul = document.createElement.bind(document, 'ul'); function magnetLinkHash(m) { return m.split(':').slice(-1)[0]; } function name(r) { var info_hash = magnetLinkHash(r.magnet); var a = document.createElement('a'); a.href = window.location.origin + '/' + info_hash + '?dn=' + encodeURIComponent(r.name.slice(0, 75)); a.addEventListener('click', aClickNav); a.appendChild(text(r.name)); return a; } function magnet(r) { var a = document.createElement('a'); a.href = r.magnet + '&dn=' + encodeURIComponent(r.name.slice(0, 75)); a.appendChild(text(String.fromCodePoint(129522) + ':?xt')); return a; } /* calculateScore :: Int -> POSIXTime -> Double calculateScore n t = (e ** ((log maxDbl / endt) * (x - t0)) - 1) * (fromIntegral n) where maxDbl = 10 ** 300 t0 = 1572391617 endt = t0 + (1.577 * 10**10) x = realToFrac t e = exp 1 inverseScore :: Double -> Integer inverseScore score = round $ ((log (score + 1)) / (log maxDbl / endt) + t0) where maxDbl = 10 ** 300 t0 = 1572391617 endt = t0 + (1.577 * 10**10) */ function currentScore() { var t0 = 1572391617; var x = Date.now() / 1000; var endt = t0 + (1.577 * 10**10) return Math.E ** ((Math.log(10 ** 300) / endt) * ((x - t0) - 1)) } function score(r) { score = Number(r.score); return text((Math.log(score / CurrentScore)).toFixed(2)); } function invertScore(score) { var t0 = 1572391617; var maxDbl = 10 ** 300 var endt = t0 + (1.577 * 10 ** 10) return Math.round((Math.log(score + 1) / (Math.log(maxDbl) / endt)) + t0); } var TableHeadings = [ ['Name', name ] , ['Size', function(r) { return text(r.total_size) }] , ['Files', function(r) { return text(r.filecount) }] , ['', magnet] , ['Score', score] ]; var TableHeadingNames = TableHeadings.map(function(t){ return t[0]; }); function keepElements(elem_selectors) { var document_root = document.querySelector('p'); document_root.innerHTML = ''; for (var i=0; i < elem_selectors.length; i++) { var sel = elem_selectors[i]; var elem = global_elements[sel]; document_root.appendChild(elem); } } function ensureElementExist(selector) { var document_root = document.querySelector('p'); var elem = document_root.querySelector(selector); if (!elem) { document_root.appendChild(global_elements[selector]); } } function loadTop() { //TODO: dispatch changeurl event cancelGlobalEvts(); ensureElementExist('style.loading'); pageInfoText.innerText = 'Loading Top 100...'; get('/cgi-bin/top_100', cancel) .then(function(response) { CurrentScore = currentScore(); console.log("Current score:", CurrentScore); var document_root = document.querySelector('p'); var t = parseTable(response); console.log(t); keepElements(['style', '.info']); pageInfoText.innerText = 'Top 100'; document_root.appendChild(assembleTable(t)); }) .catch(caught.bind(this, "Failed to load top 100: ")); } function caught(m, e) { console.error(m); console.error(e); alert(new Error(m + e.message)); }; function loadInfo(info_hash) { var preLoad_executed = false; var page = document.createElement('div'); function preLoad() { if (preLoad_executed) { return; } preLoad_executed = true; keepElements(['style', '.info']); pageInfoText.innerText = info_hash; document.querySelector('p').appendChild(page); } ensureElementExist('style.loading'); pageInfoText.innerText = 'Loading info for ' + info_hash; cancelGlobalEvts(); get('/cgi-bin/files?i=' + info_hash, cancel) .then(function(response) { preLoad(); var files = parseTable(response); console.log(files); var h = document.createElement('h2'); h.innerText = 'Contents'; page.appendChild(h); var t = renderTree(files); page.appendChild(t); }) .catch(caught.bind(this, "Failed to info details. Reason:")); get('/cgi-bin/info?i=' + info_hash, cancel) .then(function(response) { preLoad(); var info = parseTable(response)[0]; pageInfoText.innerText = info.name; var first_seen = new Date(info.added.split('.')[0]+'Z'); var mlink = magnet(info); mlink.innerText = mlink.href; var last_seen = new Date(Math.min( invertScore(Number(info.score)) * 1000, new Date().getTime()) ); prepend(page, div(text('First Indexed: ' + first_seen))); prepend(page, div(text('Approximately last seen: ' + last_seen))); var filecount = div(text('Files: ')); filecount.appendChild(bold(text(info.filecount))) prepend(page, filecount); var total_size = div(text('Total Size: ')); total_size.appendChild(bold(text(info.total_size))) prepend(page, total_size); prepend(page, div(mlink)); }) .catch(caught.bind(this, "Failed to load file list. Details:")); } function loadResults(search_query) { var encoder = new TextEncoder(); var hex = bytesToHex(encoder.encode(search_query)); console.log(hex); cancelGlobalEvts(); ensureElementExist('style.loading'); pageInfoText.innerText = 'Loading results for "' + search_query + '"...'; get('/cgi-bin/search?q=' + hex, cancel) .then(function(response) { CurrentScore = currentScore(); console.log("Current score:", CurrentScore); var t = assembleTable(parseTable(response)); var document_root = document.querySelector('p'); keepElements(['style', '.info']) pageInfoText.innerText = 'Results for "' + search_query + '"'; document_root.appendChild(t); }) .catch(caught.bind(this, "Failed to results: ")); } function changeUrl(path, search_query) { var event = new CustomEvent( 'urlchange', { detail: { path: path, query: search_query } }); window.dispatchEvent(event); } function searchInputHandler() { var input = document.querySelector("input"); var btn = document.querySelector("button"); btn.addEventListener('click', function() { var search_query = input.value; window.history.pushState(null, "", window.location.origin + '/s?q=' + encodeURIComponent(search_query)); changeUrl('/s', { q: search_query }); }); input.addEventListener('keydown', function(e) { if (e.keyCode === 13) { e.preventDefault(); btn.click(); } }); } function handleSearch(_, query_params) { var search_query = query_params.q; if (!search_query) { console.log("nothing to query"); return; } else { console.log(search_query); loadResults(search_query); } } function handleInfo(parts, query_params) { console.log('info request', parts, query_params); loadInfo(parts[0]); } function onUrlChange(e) { var path = e.detail.path.split('/').filter(function(s){ return s.length; }); console.log(e, e.detail, path); resolvePath(ROUTES, path, e.detail.query); } function aClickNav(e) { e.preventDefault(); e.stopPropagation(); console.log(this, this.href); window.history.pushState(null, "", this.href); onRealUrlChange(); } function bindEventHandlers() { window.addEventListener('urlchange', onUrlChange); window.addEventListener('popstate', onRealUrlChange); } function gatherElements() { global_elements = {}; var qs; for (var i=0; i < GlobalElemSelectors.length; i++) { var sel = GlobalElemSelectors[i]; var elem = document.querySelector(sel); global_elements[sel] = elem; } } function parseQueryParams(querystring) { return querystring.split('&') .map(function(part) { return part.split('=').map(decodeURIComponent); }) .reduce(function(acc, pair) { return { [pair[0]]: pair[1] } }, {}); } function onRealUrlChange() { var path = window.location.pathname; var query_params = parseQueryParams(window.location.search.substring(1)); changeUrl(path, query_params); } function initPageInfoText() { pageInfoText = document.createElement('div'); pageInfoText.className = 'info'; prepend(document.querySelector('p'), pageInfoText); } /** * Init */ initPageInfoText(); gatherElements(); bindEventHandlers(); searchInputHandler(); onRealUrlChange();