2023-08-24 22:07:37 +00:00
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 ) {
2023-08-24 22:07:37 +00:00
if ( arg == null ) {
continue ;
}
2023-08-24 22:07:37 +00:00
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 global _elements ;
var GlobalElemSelectors =
[ 'style'
, 'style.loading'
, '.info'
]
var GlobalVars = { } ;
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 ( "Route part:" , route _part ) ;
console . log ( "Route part fparts:" , route _part . fparts ) ;
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' ) ;
2023-08-24 22:07:37 +00:00
console . log ( path _data , route _part , query _params ) ;
2023-08-24 22:07:37 +00:00
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 ;
}
}
}
2023-08-24 22:07:37 +00:00
var ROUTES = RoutePart (
[
[ match ( /^spam_post$/ )
, new RoutePart (
[
[ match ( /^[0-9]+$/ )
, new RoutePart ( [ ] , showSpamPost )
]
]
, null
)
]
]
, handlePageRoot
) ;
2023-08-24 22:07:37 +00:00
/ * *
* Pagination
* /
function createPagination ( start _page _num , url , name , page _size , renderContent ) {
const numDisplayedPages = 5 ; // Specify the number of numbered links to display
// Helper function to update the URL with the new page number
function updatePageNumberInURL ( pageNum ) {
2023-08-24 22:07:37 +00:00
const url = new URL ( window . location . href ) ;
const query _params = url . searchParams ;
console . log ( "updatePageNumberInURL" , query _params . constructor . name ) ;
query _params . set ( replaceAll ( name , ' ' , '_' ) , pageNum ) ;
console . log ( "Hello World 1" ) ;
changeUrl ( "/" , query _params , true ) ;
2023-08-24 22:07:37 +00:00
}
function createPaginationControls ( page _num , dataset _size ) {
2023-08-24 22:07:37 +00:00
const container = document . createElement ( 'nav' ) ;
2023-08-24 22:07:37 +00:00
container . classList . add ( 'pagination' ) ;
// Calculate the total number of pages
const total _pages = Math . ceil ( dataset _size / page _size ) ;
console . log ( 'page_num:' , page _num , 'page_size:' , page _size , 'dataset_size:' , dataset _size , 'total_pages:' , total _pages ) ;
function createNumberedLink ( pageNum ) {
const link = document . createElement ( 'a' ) ;
link . addEventListener ( 'click' , function ( e ) {
//loadNthPage(url, pageNum - 1, page_size)
e . preventDefault ( ) ;
updatePageNumberInURL ( pageNum - 1 ) ;
} ) ;
link . appendChild ( text ( pageNum ) ) ;
if ( pageNum - 1 === page _num ) {
link . classList . add ( 'active' ) ; // Apply CSS class for active page
} else {
link . setAttribute ( 'href' , '#' ) ;
}
return link ;
}
// Add Previous link if applicable
if ( page _num > 0 ) {
const prev _link = document . createElement ( 'a' ) ;
prev _link . setAttribute ( 'href' , '#' ) ;
prev _link . addEventListener ( 'click' , function ( e ) {
e . preventDefault ( ) ;
//loadNthPage(url, page_num - 1, page_size)
updatePageNumberInURL ( page _num - 1 ) ;
} ) ;
prev _link . appendChild ( text ( 'Previous' ) ) ;
container . appendChild ( prev _link ) ;
}
// Add numbered links
const firstDisplayedPage = Math . max ( 1 , page _num - Math . floor ( numDisplayedPages / 2 ) ) ;
const lastDisplayedPage = Math . min ( total _pages , firstDisplayedPage + numDisplayedPages - 1 ) ;
if ( firstDisplayedPage > 1 ) {
container . appendChild ( createNumberedLink ( 1 ) ) ;
if ( firstDisplayedPage > 2 ) {
container . appendChild ( text ( '...' ) ) ;
}
}
for ( let i = firstDisplayedPage ; i <= lastDisplayedPage ; i ++ ) {
container . appendChild ( createNumberedLink ( i ) ) ;
}
if ( lastDisplayedPage < total _pages ) {
if ( lastDisplayedPage < total _pages - 1 ) {
container . appendChild ( text ( '...' ) ) ;
}
container . appendChild ( createNumberedLink ( total _pages ) ) ;
}
// Add Next link if applicable
if ( page _num < total _pages - 1 ) {
const next _link = document . createElement ( 'a' ) ;
next _link . setAttribute ( 'href' , '#' ) ;
next _link . addEventListener ( 'click' , function ( e ) {
//loadNthPage(url, page_num + 1, page_size)
e . preventDefault ( ) ;
updatePageNumberInURL ( page _num + 1 ) ;
} ) ;
next _link . appendChild ( text ( 'Next' ) ) ;
container . appendChild ( next _link ) ;
}
return container ;
}
function loadNthPage ( url , page _num ) {
// Calculate the range of results based on the current page
const start = page _num * page _size ;
const end = start + page _size - 1 ;
var headers = {
'Authorization' : 'Bearer ' + GlobalVars . settings . jwt ,
//'Prefer': 'count=estimated',
'Prefer' : 'count=exact' ,
'Range-Unit' : 'items' ,
'Range' : ` ${ start } - ${ end } `
} ;
pageInfoText . innerText = ` Loading page ${ page _num } of ${ name } ` ;
cancelGlobalEvts ( ) ;
return get ( url , cancel , headers )
. then ( function ( response ) {
// Retrieve the range and total number of results from the HTTP headers
const rangeHeader = response . getResponseHeader ( 'Content-Range' ) ;
const [ start , end , total _results ] = parseRangeHeader ( rangeHeader ) ;
const json = JSON . parse ( response . responseText ) ;
const controlsA = createPaginationControls ( page _num , total _results ) ;
const controlsB = createPaginationControls ( page _num , total _results ) ;
renderContent ( json , controlsA , controlsB ) ;
2023-08-24 22:07:37 +00:00
pageInfoText . innerText = ` ${ name } page ${ page _num + 1 } / ${ Math . ceil ( total _results / page _size ) } ` ;
2023-08-24 22:07:37 +00:00
} )
. catch ( caught . bind ( this , ` Failed to load ${ url } page ${ page _num } . Reason: ` ) ) ;
}
loadNthPage ( url , start _page _num ) ;
}
// Helper function to parse the Content-Range header
function parseRangeHeader ( rangeHeader ) {
const match = rangeHeader . match ( /(\d+)-(\d+)\/(\d+)/ ) ;
if ( match ) {
const start = parseInt ( match [ 1 ] ) ;
const end = parseInt ( match [ 2 ] ) ;
const totalResults = parseInt ( match [ 3 ] ) ;
return [ start , end , totalResults ] ;
}
return [ 0 , 0 , 0 ] ; // Default values if the header is not available or not in the expected format
}
2023-08-24 22:07:37 +00:00
/ * *
* Functions
* /
function resolveResponse ( resolve , reject ) {
return function ( e ) {
2023-08-24 22:07:37 +00:00
const status = e . target . getResponseHeader ( "Status" ) ;
2023-08-24 22:07:37 +00:00
if ( status != null ) {
2023-08-24 22:07:37 +00:00
const status _code = Number ( status . split ( " " ) [ 0 ] ) ;
2023-08-24 22:07:37 +00:00
if ( status _code >= 200 && status _code < 300 ) {
resolve ( e . target . responseText ) ;
} else {
2023-08-24 22:07:37 +00:00
reject ( new Error ( 'API responded with ' + status + ". " + e . target . responseText ) ) ;
2023-08-24 22:07:37 +00:00
}
} else {
if ( e . target . status >= 200 && e . target . status < 300 ) {
2023-08-24 22:07:37 +00:00
return resolve ( e . target ) ;
2023-08-24 22:07:37 +00:00
} else {
2023-08-24 22:07:37 +00:00
reject ( new Error ( 'API responded with ' + e . target . status + ". " + e . target . responseText ) ) ;
2023-08-24 22:07:37 +00:00
}
}
}
}
function cancelGlobalEvts ( ) {
while ( GlobalCancellableEvents . length ) {
GlobalCancellableEvents . pop ( ) . abort ( ) ;
}
}
function get ( url , 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 ( 'GET' , url ) ;
if ( headers ) {
Object . entries ( headers )
. forEach ( function ( [ header _name , header _value ] ) {
req . setRequestHeader ( header _name , header _value ) ;
} ) ;
}
req . send ( ) ;
} ) ;
}
2023-08-24 22:07:37 +00:00
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 ( ) ;
}
} ) ;
}
2023-08-24 22:07:37 +00:00
// Given a BigInt that is packed bytes, unpack the bytes into an ArrayBuffer
function unpackBytes ( bigint ) {
const buffer = new ArrayBuffer ( 8 ) ;
const view = new DataView ( buffer ) ;
view . setBigInt64 ( 0 , bigint ) ;
return buffer ;
}
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
function bufferToHex ( buffer ) {
2023-08-24 22:07:37 +00:00
// Create a Uint8Array view to access the bytes
const uint8Array = new Uint8Array ( buffer ) ;
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
// Convert each byte to its hexadecimal representation
const hexArray = Array . from ( uint8Array , byte => byte . toString ( 16 ) . padStart ( 2 , '0' ) ) ;
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
// Join the hexadecimal values into a single string
2023-08-24 22:07:37 +00:00
return hexArray . join ( '' ) ;
2023-08-24 22:07:37 +00:00
}
2023-08-24 22:07:37 +00:00
function hammingDistance ( buffer1 , buffer2 ) {
if ( buffer1 . byteLength !== buffer2 . byteLength ) {
throw new Error ( "Buffer lengths must be equal" ) ;
}
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
const view1 = new DataView ( buffer1 ) ;
const view2 = new DataView ( buffer2 ) ;
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
let distance = 0 ;
for ( let i = 0 ; i < buffer1 . byteLength ; i ++ ) {
const byte1 = view1 . getUint8 ( i ) ;
const byte2 = view2 . getUint8 ( i ) ;
let xor = byte1 ^ byte2 ;
while ( xor !== 0 ) {
distance ++ ;
xor &= xor - 1 ;
}
}
return distance ;
}
2023-08-24 22:07:37 +00:00
function text ( s ) {
return document . createTextNode ( s ) ;
}
function _mkelem ( etype , child ) {
var elem = document . createElement ( etype ) ;
if ( child ) {
elem . appendChild ( child ) ;
}
return elem ;
}
2023-08-24 22:07:37 +00:00
var h3 = _mkelem . bind ( this , 'h3' ) ;
2023-08-24 22:07:37 +00:00
var h4 = _mkelem . bind ( this , 'h4' ) ;
2023-08-24 22:07:37 +00:00
var div = _mkelem . bind ( this , 'div' ) ;
var li = _mkelem . bind ( this , 'li' ) ;
var span = _mkelem . bind ( this , 'span' ) ;
2023-08-24 22:07:37 +00:00
var italic = _mkelem . bind ( this , 'i' ) ;
2023-08-24 22:07:37 +00:00
var bold = _mkelem . bind ( this , 'b' ) ;
var ul = _mkelem . bind ( this , 'ul' ) ;
2023-08-24 22:07:37 +00:00
function keepElements ( elem _selectors ) {
var document _root = document . querySelector ( 'main' ) ;
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 caught ( m , e ) {
console . error ( m ) ;
console . error ( e ) ;
2023-08-24 22:07:37 +00:00
for ( var property in e ) {
console . error ( property + ": " + e [ property ] ) ;
}
2023-08-24 22:07:37 +00:00
alert ( new Error ( m + e . message ) ) ;
} ;
function mkSpamImgElement ( img _description ) {
var img _elem = document . createElement ( 'img' ) ;
var mime = img _description . mimetype ;
2023-08-24 22:07:37 +00:00
var thumb _url ;
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
var illegal = img _description . illegal ;
console . log ( 'img_description.illegal' , img _description . illegal ) ;
if ( illegal ) {
2023-08-24 22:09:08 +00:00
thumb _url = "/static/redacted.jpg" ;
2023-08-24 22:07:37 +00:00
} else if ( mime == "audio/mpeg" ) {
2023-08-24 22:09:08 +00:00
thumb _url = '/static/mpg.png' ;
2023-08-24 22:07:37 +00:00
} else {
2023-09-06 22:19:06 +00:00
thumb _url = ` /bin/?path= ${ img _description . md5 _hash } _thumbnail.jpg&mimetype=image/jpeg `
2023-08-24 22:07:37 +00:00
}
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
img _elem . setAttribute ( 'src' , thumb _url ) ;
img _elem . setAttribute ( 'decoding' , 'async' ) ;
img _elem . setAttribute ( 'loading' , 'lazy' ) ;
2023-08-24 22:07:37 +00:00
var a _elem ;
if ( ! illegal ) {
2023-09-06 22:19:06 +00:00
var image _url = ` /bin/?path= ${ img _description . md5 _hash } .attachment&mimetype= ${ mime } `
2023-08-24 22:07:37 +00:00
a _elem = document . createElement ( 'a' ) ;
a _elem . setAttribute ( 'href' , image _url ) ;
a _elem . appendChild ( img _elem ) ;
} else {
a _elem = div ( img _elem ) ;
}
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
var img _container _elem = div ( a _elem ) ;
img _container _elem . classList . add ( 'post--image_container' ) ;
2023-08-24 22:07:37 +00:00
return img _container _elem ;
}
2023-08-24 22:07:37 +00:00
function renderNav ( ) {
var nav = document . createElement ( 'nav' ) ;
var a _elem = _mkelem ( 'a' , text ( 'All Known Spam Posts' ) ) ;
2023-08-24 22:07:37 +00:00
a _elem . addEventListener ( 'click' , function ( e ) {
e . preventDefault ( ) ;
2023-08-24 22:07:37 +00:00
changeUrl ( '/' , new URLSearchParams ( ) , true ) ;
2023-08-24 22:07:37 +00:00
} ) ;
a _elem . setAttribute ( 'href' , '#' ) ;
nav . appendChild ( a _elem ) ;
return nav
}
2023-08-24 22:07:37 +00:00
function linkToBoard ( website _name , board _name , thread _id ) {
let root = ` ${ GlobalVars . settings . website _urls [ website _name ] } / ${ board _name } `
console . log ( GlobalVars . settings . website _urls , GlobalVars . settings . website _urls [ website _name ] , board _name ) ;
if ( thread _id ) {
return root + ` /res/ ${ thread _id } .html ` ;
} else {
return root + 'index.html' ;
}
}
2023-08-24 22:07:37 +00:00
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 ;
}
2023-08-24 22:09:08 +00:00
function onClickMarkIllegal ( phashes , e ) {
2023-08-24 22:07:37 +00:00
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
2023-08-24 22:09:08 +00:00
console . log ( 'CLICK' , phashes , e . target . hash ) ;
2023-08-24 22:07:37 +00:00
2023-08-24 22:09:08 +00:00
// Get the whole post div (parent of the anchor tag)
const postDiv = e . target . closest ( '.post' ) ;
const img _elems = postDiv . querySelectorAll ( '.post--image_container img' ) ;
window . requestAnimationFrame ( function ( ) {
img _elems . forEach ( function ( elem ) {
elem . classList . add ( 'censored' ) ;
} ) ;
window . requestAnimationFrame ( async function ( ) {
const alert _result = confirm ( "Warning! This action deletes all image file data for images similar to the ones you selected. This action is irreversible. Image fingerprints will be kept for matching in the future. Do you want to proceed?" ) ;
2023-08-24 22:07:37 +00:00
2023-08-24 22:09:08 +00:00
if ( alert _result ) {
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 . hash = e . target . hash ;
location . reload ( ) ;
} )
. catch ( caught . bind ( this , "Failed to mark post illegal because:" ) ) ;
} else {
img _elems . forEach ( function ( elem ) {
elem . classList . remove ( 'censored' ) ;
} ) ;
}
} ) ;
} ) ;
2023-08-24 22:07:37 +00:00
}
2023-08-24 22:07:37 +00:00
function renderPostElem ( post ) {
2023-08-24 22:07:37 +00:00
const postContainer = div ( ) ;
2023-08-24 22:07:37 +00:00
postContainer . classList . add ( 'post' ) ;
2023-08-24 22:09:08 +00:00
const identifier = "text_post_" + post . text _post _id ;
postContainer . id = identifier ;
2023-08-24 22:07:37 +00:00
// Header with timestamp, link to the thread
const postHeader = div ( ) ;
postHeader . classList . add ( 'post--header' ) ;
postContainer . appendChild ( postHeader ) ;
console . log ( 'renderPostElem POST:' , post ) ;
if ( post . website _name != null ) {
const link _url = linkToBoard ( post . website _name , post . board _name , post . thread _id ) ;
const boardlink = span ( )
const boardlink _a = document . createElement ( 'a' ) ;
boardlink _a . appendChild ( text ( post . website _name ) ) ;
boardlink _a . setAttribute ( 'href' , link _url ) ;
boardlink _a . setAttribute ( 'title' , 'Destination this post was originally headed for (thread or board)' ) ;
boardlink . appendChild ( text ( '[' ) ) ;
boardlink . appendChild ( boardlink _a ) ;
boardlink . appendChild ( text ( ']' ) ) ;
postHeader . appendChild ( boardlink ) ;
}
postHeader . appendChild ( span ( text ( ( new Date ( post . time _stamp ) . toUTCString ( ) ) ) ) ) ;
2023-08-24 22:07:37 +00:00
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' ) ) ;
2023-08-24 22:09:08 +00:00
mark _illegal _a . setAttribute ( 'href' , '#' + identifier ) ;
2023-08-24 22:07:37 +00:00
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 ) ;
}
2023-08-24 22:07:37 +00:00
postContainer . appendChild ( postHeader ) ;
const postElem = div ( ) ;
2023-08-24 22:07:37 +00:00
postElem . classList . add ( 'post--body_container' ) ;
postContainer . appendChild ( postElem ) ;
2023-08-24 22:07:37 +00:00
const bodyTextElem = div ( ) ;
2023-08-24 22:07:37 +00:00
bodyTextElem . classList . add ( 'post--body' ) ;
bodyTextElem . innerText = post . body ;
2023-08-24 22:07:37 +00:00
if ( post . known _spam _post _attachment ) {
post . known _spam _post _attachment . forEach ( function ( attachment ) {
postElem . appendChild ( mkSpamImgElement ( attachment ) ) ;
} ) ;
}
2023-08-24 22:07:37 +00:00
postElem . appendChild ( bodyTextElem ) ;
return postContainer ;
}
2023-08-24 22:07:37 +00:00
function renderKnownSpamAttachments ( attachments ) {
var container = div ( ) ;
container . classList . add ( 'attachment' ) ;
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
attachments . forEach ( function ( a ) {
container . appendChild ( mkSpamImgElement ( a ) ) ;
} ) ;
2023-08-24 22:07:37 +00:00
return container ;
}
function renderThings ( things _map ) {
var container = div ( ) ;
for ( const [ key , value ] of Object . entries ( things _map ) ) {
var entry = div ( ) ;
entry . appendChild ( span ( bold ( text ( key + ": " ) ) ) ) ;
entry . appendChild ( span ( text ( value ) ) ) ;
container . appendChild ( entry ) ;
}
return container ;
}
2023-08-24 22:07:37 +00:00
function renderAttachmentReason ( attachment , known _spam _post _attachments ) {
let container = div ( ) ;
2023-08-24 22:07:37 +00:00
container . classList . add ( 'attachment-reason' ) ;
container . appendChild ( mkSpamImgElement ( attachment ) ) ;
2023-08-24 22:07:37 +00:00
let phash _buffer = unpackBytes ( BigInt ( attachment [ 'phash' ] ) ) ;
let distance = ( 64 - Math . min ( ... known _spam _post _attachments . map ( function ( known _attachment ) {
let known _buffer = unpackBytes ( BigInt ( known _attachment [ 'phash' ] ) ) ;
let distance = hammingDistance ( known _buffer , phash _buffer ) ;
return distance ;
} ) ) ) / 64 ;
let things = {
2023-08-24 22:07:37 +00:00
'mimetype' : attachment [ 'mimetype' ] ,
'time stamp' : attachment [ 'time_stamp' ] ,
'md5' : attachment [ 'md5_hash' ] ,
2023-08-24 22:07:37 +00:00
'phash (hex)' : bufferToHex ( phash _buffer ) ,
'match' : ` ${ Math . round ( distance * 100 * 10 ) / 10 } % `
2023-08-24 22:07:37 +00:00
} ;
container . appendChild ( renderThings ( things ) ) ;
2023-08-24 22:07:37 +00:00
return container ;
}
function renderReasons ( post ) {
var container = div ( h3 ( text ( 'Reasons for being considered spam:' ) ) ) ;
container . appendChild ( renderReasonsUl ( post ) ) ;
var reasonDetails = post . reason _details ;
2023-08-24 22:09:08 +00:00
var illegal = isIllegal ( post ) ;
2023-08-24 22:07:37 +00:00
if ( reasonDetails ) {
var reasonDetailsContainer = div ( ) ;
reasonDetailsContainer . className = 'reason-details' ;
if ( reasonDetails . frequent _attachment _details ) {
2023-08-24 22:07:37 +00:00
reasonDetailsContainer . appendChild ( h4 ( text ( 'Previously posted matching attachments:' ) ) ) ;
2023-08-24 22:07:37 +00:00
reasonDetails . frequent _attachment _details . forEach ( function ( attachment ) {
2023-08-24 22:09:08 +00:00
attachment . illegal = illegal ;
2023-08-24 22:07:37 +00:00
reasonDetailsContainer . appendChild (
renderAttachmentReason ( attachment , post . known _spam _post _attachment )
) ;
2023-08-24 22:07:37 +00:00
} ) ;
}
if ( reasonDetails . frequent _text _details ) {
var textPosts = div ( ) ;
reasonDetails . frequent _text _details . forEach ( function ( post ) {
textPosts . appendChild ( renderPostElem ( post ) ) ;
} ) ;
reasonDetailsContainer . appendChild ( h4 ( text ( 'Previously posted matching text:' ) ) ) ;
reasonDetailsContainer . appendChild ( textPosts ) ;
}
container . appendChild ( h3 ( text ( 'Reason Details:' ) ) ) ;
container . appendChild ( reasonDetailsContainer ) ;
}
return container ;
}
2023-08-24 22:07:37 +00:00
var REASONS = {
2023-08-24 22:09:08 +00:00
RECENTLY _POSTED _ATTACHMENT _ABOVE _RATE : 1 << 0 ,
LEXICAL _ANALYSIS _SHOWS _NONSENSE : 1 << 1 ,
SEGMENTED _AVG _ENTROPY _TOO _HIGH : 1 << 2 ,
TEXT _MATCHES _KNOWN _SPAM : 1 << 3 ,
RECENTLY _POSTED _TEXT _ABOVE _RATE : 1 << 4 ,
TEXT _IS _URL : 1 << 5 ,
TEXT _IS _ONE _LONG _WORD : 1 << 6 ,
TEXT _IS _RANDOM _WORDS : 1 << 7 ,
ATTACHMENT _MATCHES _KNOWN _SPAM : 1 << 8 ,
TEXT _MANUALLY _MARKED _AS _SPAM : 1 << 9 ,
ATTACHMENT _MANUALLY _MARKED _AS _SPAM : 1 << 10
2023-08-24 22:07:37 +00:00
} ;
function reasonsFromBitmap ( n ) {
var reasons = new Set ( ) ;
for ( const [ reason , bit ] of Object . entries ( REASONS ) ) {
if ( n & bit ) {
reasons . add ( reason ) ;
}
}
return reasons ;
}
2023-08-24 22:07:37 +00:00
function renderReasonsUl ( post ) {
2023-08-24 22:07:37 +00:00
var reasons = ul ( ) ;
reasonsFromBitmap ( post . reason ) . forEach ( function ( r ) {
reasons . appendChild ( li ( text ( r ) ) ) ;
} ) ;
2023-08-24 22:07:37 +00:00
return reasons ;
}
function renderOverviewPost ( post ) {
var postContainer = renderPostElem ( post ) ;
2023-08-24 22:07:37 +00:00
postContainer . appendChild ( h3 ( text ( 'Reasons:' ) ) ) ;
2023-08-24 22:07:37 +00:00
postContainer . appendChild ( div ( renderReasonsUl ( post ) ) ) ;
2023-08-24 22:07:37 +00:00
postContainer . classList . add ( 'post--overview' ) ;
2023-08-24 22:07:37 +00:00
return postContainer ;
}
2023-08-24 22:07:37 +00:00
function replaceAll ( input , find , replace ) {
var regex = new RegExp ( find , "g" ) ;
return input . replace ( regex , replace ) ;
}
2023-08-24 22:07:37 +00:00
function loadKnownSpamPosts ( params ) {
2023-08-24 22:07:37 +00:00
console . log ( "loadKnownSpamPosts" ) ;
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
const pageName = "known spam posts" ;
2023-08-24 22:07:37 +00:00
console . log ( "HELLO WORLD 1" ) ;
2023-08-24 22:07:37 +00:00
var url = GlobalVars . settings . postgrest _url + '/known_spam_post?select=*,known_spam_post_attachment(*,phash::text)'
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
const pageNum = Number ( params . get ( replaceAll ( pageName , ' ' , '_' ) ) || 0 ) ;
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
function renderContent ( json , controlsA , controlsB ) {
var mainElem = document . querySelector ( 'main' ) ;
mainElem . innerHTML = '' ;
mainElem . appendChild ( controlsA ) ;
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
json . forEach ( function ( post ) {
var postElem = renderOverviewPost ( post ) ;
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
postElem . addEventListener ( 'click' ,
changeUrl . bind (
this ,
'/spam_post/' + post . text _post _id ,
new URLSearchParams ( ) ,
true
) ) ;
2023-08-24 22:07:37 +00:00
mainElem . appendChild ( postElem ) ;
} ) ;
mainElem . appendChild ( controlsB ) ;
2023-08-24 22:09:08 +00:00
if ( window . location . hash ) {
const elem = document . querySelector ( window . location . hash ) ;
if ( elem ) {
elem . scrollIntoView ( ) ;
}
}
2023-08-24 22:07:37 +00:00
}
createPagination ( pageNum , url , pageName , 25 , renderContent ) ;
2023-08-24 22:07:37 +00:00
}
2023-08-24 22:07:37 +00:00
function changeUrl ( path , search _query , push _state ) {
2023-08-24 22:07:37 +00:00
console . log ( 'changeURL' ) ;
2023-08-24 22:07:37 +00:00
var event = new CustomEvent (
'urlchange' ,
{
detail : {
path : path ,
2023-08-24 22:07:37 +00:00
query : search _query ,
push _state : push _state ,
2023-08-24 22:07:37 +00:00
}
} ) ;
window . dispatchEvent ( event ) ;
}
function handlePageRoot ( _ , query _params ) {
2023-08-24 22:07:37 +00:00
console . log ( "handlePageRoot" , JSON . stringify ( query _params ) ) ;
2023-08-24 22:07:37 +00:00
return loadKnownSpamPosts ( query _params ) ;
2023-08-24 22:07:37 +00:00
}
2023-08-24 22:07:37 +00:00
function showSpamPost ( path _data , _ ) {
console . log ( path _data ) ;
var post _id = path _data [ 1 ] ;
pageInfoText . innerText = 'Loading known spam post ' + post _id ;
var url =
GlobalVars . settings . postgrest _url
+ '/known_spam_post?text_post_id=eq.' + post _id
2023-08-24 22:07:37 +00:00
+ '&select=*,known_spam_post_attachment(*,phash::text)' ; // Have to load the phash as text here because javascripts json parser won't use BigInt when appropriate because javascript Numbers are based on floats and we're dealing with a 64bit signed int.
2023-08-24 22:07:37 +00:00
var headers = {
'Authorization' : 'Bearer ' + GlobalVars . settings . jwt
} ;
var postSectionElem = document . createElement ( 'section' ) ;
var mainElem = document . querySelector ( 'main' ) ;
mainElem . innerHTML = '' ;
2023-08-24 22:07:37 +00:00
mainElem . appendChild ( renderNav ( ) ) ;
2023-08-24 22:07:37 +00:00
mainElem . appendChild ( postSectionElem ) ;
var h2 = document . createElement ( 'h2' ) ;
h2 . innerText = 'Known Spam Attachments' ;
mainElem . appendChild ( postSectionElem ) ;
mainElem . appendChild ( h2 ) ;
var knownSpamAttachmentsSectionElem = document . createElement ( 'section' ) ;
mainElem . append ( knownSpamAttachmentsSectionElem ) ;
2023-08-24 22:07:37 +00:00
var reasonsSectionElem = document . createElement ( 'section' ) ;
mainElem . appendChild ( reasonsSectionElem ) ;
2023-08-24 22:07:37 +00:00
get ( url , cancel , headers )
. then ( function ( response ) {
2023-08-24 22:07:37 +00:00
var json = JSON . parse ( response . responseText ) ;
2023-08-24 22:07:37 +00:00
console . log ( json ) ;
pageInfoText . innerText = 'Known Spam Post' ;
json . forEach ( function ( p ) {
var postElem = renderPostElem ( p ) ;
postSectionElem . appendChild ( postElem ) ;
2023-08-24 22:07:37 +00:00
reasonsSectionElem . appendChild ( renderReasons ( p ) )
2023-08-24 22:07:37 +00:00
} ) ;
} )
. catch ( caught . bind ( this , "Failed to load known spam post. Reason:" ) ) ;
url =
GlobalVars . settings . postgrest _url
+ '/known_spam_attachments?post_id=eq.' + post _id ;
get ( url , cancel , headers )
. then ( function ( response ) {
2023-08-24 22:07:37 +00:00
var json = JSON . parse ( response . responseText ) ;
2023-08-24 22:07:37 +00:00
console . log ( "known spam attachments:" , json ) ;
2023-08-24 22:07:37 +00:00
knownSpamAttachmentsSectionElem
. appendChild ( renderKnownSpamAttachments ( json ) ) ;
2023-08-24 22:07:37 +00:00
} )
. catch ( caught . bind ( this , "Failed to load known spam attachments. Reason:" ) ) ;
}
2023-08-24 22:07:37 +00:00
function onUrlChange ( e ) {
2023-08-24 22:07:37 +00:00
console . log ( 'Hello World 2' ) ;
2023-08-24 22:07:37 +00:00
var path = e . detail . path . split ( '/' ) . filter ( function ( s ) { return s . length ; } ) ;
console . log ( e , e . detail , path ) ;
2023-08-24 22:07:37 +00:00
if ( e . detail . push _state ) {
2023-08-24 22:07:37 +00:00
var search _query = e . detail . query ;
search _query . set ( '__path' , e . detail . path ) ;
console . log ( "onUrlChange push_state search_query:" , search _query , typeof search _query ) ;
2023-08-24 22:07:37 +00:00
window . history . pushState ( null , "" ,
window . location . origin + window . location . pathname + '?' + search _query . toString ( ) ) ;
}
2023-08-24 22:07:37 +00:00
resolvePath ( ROUTES , path , e . detail . query ) ;
}
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 onRealUrlChange ( ) {
2023-08-24 22:07:37 +00:00
const url = new URL ( window . location . href ) ;
const query _params = url . searchParams ;
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
const path = query _params . get ( '__path' ) || '/' ;
2023-08-24 22:07:37 +00:00
2023-08-24 22:07:37 +00:00
changeUrl ( path , query _params ) ;
}
function initPageInfoText ( ) {
pageInfoText = document . createElement ( 'div' ) ;
pageInfoText . className = 'info' ;
prepend ( document . querySelector ( '.info-container' ) , pageInfoText ) ;
}
function getSettings ( ) {
cancelGlobalEvts ( ) ;
pageInfoText . innerText = 'Loading settings' ;
return get ( '/static/settings.json' , cancel )
. then ( function ( response ) {
2023-08-24 22:07:37 +00:00
var result = JSON . parse ( response . responseText ) ;
2023-08-24 22:07:37 +00:00
console . log ( "settings response:" , result ) ;
GlobalVars . settings = result ;
pageInfoText . innerText = 'Have settings.' ;
} )
. catch ( caught . bind ( this , "Failed to load JWT: " ) ) ;
}
/ * *
* Init
* /
initPageInfoText ( ) ;
// gatherElements();
bindEventHandlers ( ) ;
// searchInputHandler();
getSettings ( )
. then ( onRealUrlChange ) ;