diff --git a/redacted.jpg b/redacted.jpg new file mode 100644 index 0000000..5068e26 Binary files /dev/null and b/redacted.jpg differ diff --git a/script.js b/script.js index 53b8a7a..f633f75 100644 --- a/script.js +++ b/script.js @@ -298,19 +298,21 @@ function parseRangeHeader(rangeHeader) { function resolveResponse(resolve, reject) { return function(e) { - var status = e.target.getResponseHeader("Status"); + const status = e.target.getResponseHeader("Status"); + if (status != null) { - var status_code = Number(status.split(" ")[0]); + const 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)); + reject(new Error('API responded with ' + status + ". " + e.target.responseText)); } } else { if (e.target.status >= 200 && e.target.status < 300) { return resolve(e.target); } else { - reject(new Error('API responded with ' + e.target.status)); + reject(new Error('API responded with ' + e.target.status + ". " + e.target.responseText)); } } } @@ -341,6 +343,31 @@ function get(url, xhr_req_follower, headers) { }); } +function post(url, body, xhr_req_follower, headers) { + return new Promise(function (resolve, reject) { + var req = new XMLHttpRequest(); + xhr_req_follower(req); + req.onload = resolveResponse(resolve, reject); + req.onerror = reject; + req.open('POST', url); + + if (headers) { + Object.entries(headers) + .forEach(function([header_name, header_value]) { + req.setRequestHeader(header_name, header_value); + }); + } + + req.setRequestHeader('Content-Type', 'application/json'); + + if (body) { + req.send(JSON.stringify(body)); + } else { + req.send(); + } + }); +} + // Given a BigInt that is packed bytes, unpack the bytes into an ArrayBuffer function unpackBytes(bigint) { const buffer = new ArrayBuffer(8); @@ -384,100 +411,6 @@ function hammingDistance(buffer1, buffer2) { return distance; } -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); } @@ -527,20 +460,31 @@ function mkSpamImgElement(img_description) { var mime = img_description.mimetype; var thumb_url; - if (mime == "audio/mpeg") { + var illegal = img_description.illegal; + console.log('img_description.illegal', img_description.illegal); + + if (illegal) { + thumb_url = `/bin/?path=${GlobalVars.settings.redacted_thumbnail_path}&mimetype=image/jpeg` + } else if (mime == "audio/mpeg") { thumb_url = `/bin/?path=${GlobalVars.settings.audio_mpeg_thumbnail_path}&mimetype=image/jpeg` } else { thumb_url = `/bin/?path=${GlobalVars.settings.content_directory}/${img_description.md5_hash}_thumbnail.jpg&mimetype=image/jpeg` } - var image_url = `/bin/?path=${GlobalVars.settings.content_directory}/${img_description.md5_hash}.attachment&mimetype=${mime}` img_elem.setAttribute('src', thumb_url); img_elem.setAttribute('decoding', 'async'); img_elem.setAttribute('loading', 'lazy'); - var a_elem = document.createElement('a'); - a_elem.setAttribute('href', image_url); - a_elem.appendChild(img_elem); + var a_elem; + + if (!illegal) { + var image_url = `/bin/?path=${GlobalVars.settings.content_directory}/${img_description.md5_hash}.attachment&mimetype=${mime}` + a_elem = document.createElement('a'); + a_elem.setAttribute('href', image_url); + a_elem.appendChild(img_elem); + } else { + a_elem = div(img_elem); + } var img_container_elem = div(a_elem); img_container_elem.classList.add('post--image_container'); @@ -570,6 +514,49 @@ function linkToBoard(website_name, board_name, thread_id) { } } +function attachmentCanBeMarkedIllegal(attachment) { + return attachment.phash != null && !attachment.illegal; +} + +function shouldDisplayMarkIllegalButton(post) { + let result = false; + + for (let attachment of (post.known_spam_post_attachment || [])) { + result = result || attachmentCanBeMarkedIllegal(attachment); + } + + return result; +} + +function isIllegal(post) { + let result = false; + + for (let attachment of (post.known_spam_post_attachment || [])) { + result = result || attachment.illegal; + } + + return result; +} + +async function onClickMarkIllegal(phashes, e) { + console.log('CLICK', phashes); + e.stopPropagation(); + e.preventDefault(); + + const url = new URL('/illegal', window.location.origin); + phashes.forEach(function(p) { + console.log('onClickMarkIllegal', typeof p, p); + url.searchParams.append('phash', p); + }); + + await post(url.toString(), null, cancel) + .then(function() { + location.reload(); + }) + .catch(caught.bind(this, "Failed to mark post illegal because:")); + +} + function renderPostElem(post) { const postContainer = div(); postContainer.classList.add('post'); @@ -599,6 +586,34 @@ function renderPostElem(post) { postHeader.appendChild(span(text((new Date(post.time_stamp).toUTCString())))); + if (shouldDisplayMarkIllegalButton(post)) { + const mark_illegal = span() + mark_illegal.classList.add('post--mark_illegal'); + const mark_illegal_a = document.createElement('a'); + + mark_illegal_a.appendChild(text('Mark Illegal')); + + mark_illegal_a.setAttribute('href', '#'); + mark_illegal_a.setAttribute('title', 'Permanently delete associated pictures and thumbnails but keep metadata for future matching'); + mark_illegal.appendChild(text('[')); + mark_illegal.appendChild(mark_illegal_a); + mark_illegal.appendChild(text(']')); + + const phashes = (post.known_spam_post_attachment || []) + .map(function(p) { + return bufferToHex(unpackBytes(BigInt(p.phash))); + }); + + mark_illegal_a.addEventListener('click', + onClickMarkIllegal.bind(this, phashes)); + + postHeader.appendChild(mark_illegal); + } else if (isIllegal(post)) { + const mark_illegal = span(text('illegal')) + mark_illegal.classList.add('post--is_illegal'); + postHeader.appendChild(mark_illegal); + } + postContainer.appendChild(postHeader); const postElem = div(); @@ -767,9 +782,13 @@ function loadKnownSpamPosts(params) { json.forEach(function (post) { var postElem = renderOverviewPost(post); - postElem.addEventListener('click', function() { - changeUrl('/spam_post/' + post.text_post_id, new URLSearchParams(), true); - }); + postElem.addEventListener('click', + changeUrl.bind( + this, + '/spam_post/' + post.text_post_id, + new URLSearchParams(), + true + )); mainElem.appendChild(postElem); }); diff --git a/style.css b/style.css index 871b502..5b7899a 100644 --- a/style.css +++ b/style.css @@ -64,6 +64,20 @@ header { flex: 1; } +.post--header span.post--mark_illegal { + flex-basis: fit-content; + flex-grow: 0; +} + +.post--header span.post--is_illegal { + flex-basis: fit-content; + flex-grow: 0; + color: orange; + font-weight: bold; + font-family: monospace; + text-transform: uppercase; +} + nav a { margin: 1rem; font-size: 1.5em;