@ -6,6 +6,7 @@
// @include http*://archive.nyafuu.org/bant/*
// @include http*://archive.nyafuu.org/bant/*
// @include http*://archived.moe/bant/*
// @include http*://archived.moe/bant/*
// @include http*://thebarchive.com/bant/*
// @include http*://thebarchive.com/bant/*
// @include http*://nineball.party/*
// @exclude http*://boards.4chan.org/bant/catalog
// @exclude http*://boards.4chan.org/bant/catalog
// @exclude http*://archive.nyafuu.org/bant/statistics/
// @exclude http*://archive.nyafuu.org/bant/statistics/
// @exclude http*://archived.moe/bant/statistics/
// @exclude http*://archived.moe/bant/statistics/
@ -29,10 +30,7 @@ const debugMode = false;
// DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR FLAGS SHOULD BE CONFIGURED USING THE CONFIGURATION BOXES
// DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR FLAGS SHOULD BE CONFIGURED USING THE CONFIGURATION BOXES
//
//
const postRemoveCounter = 60 ;
const postRemoveCounter = 60 ;
const requestRetryInterval = 5000 ; // TODO: maybe a max retries counter?
const requestRetryInterval = 5000 ; // TODO: maybe a max retries counter as well?
const regionDivider = "||" ; //TODO: We can probably remove this and seperate by ,
const is _archive = window . location . host !== "boards.4chan.org" ;
const boardID = "bant" ; //TODO: Hardcode /bant/ or accept other boards.
const version = 2 ; // Breaking changes.
const version = 2 ; // Breaking changes.
const back _end = 'https://flags.plum.moe/' ;
const back _end = 'https://flags.plum.moe/' ;
const api _flags = 'api/flags' ;
const api _flags = 'api/flags' ;
@ -45,14 +43,21 @@ const max_flags = 30;
var regions = [ ] ; // The flags we have selected.
var regions = [ ] ; // The flags we have selected.
var postNrs = [ ] ; // all post numbers in the thread.
var postNrs = [ ] ; // all post numbers in the thread.
var board _id = "" ; // The board we get flags for.
const site = {
fourchan : window . location . host === 'boards.4chan.org' ,
nineball : window . location . host === 'nineball.party'
} ;
// There are multiple foolfuuka archives we support; this has to be called after site is initialised as to not check each of them.
// I.E. we'd have to go window.location.host === 'archive.nyafuu.org' || window.location.host === 'archived.moe'
site . foolfuuka = ! site . fourchan && ! site . nineball ;
//
//
// DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR FLAGS SHOULD BE CONFIGURED USING THE CONFIGURATION BOXES
// DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR FLAGS SHOULD BE CONFIGURED USING THE CONFIGURATION BOXES
//
//
const elementsInClass = x => document . getElementsByClassName ( x ) ;
const sliceCall = x => Array . prototype . slice . call ( x ) ;
const sliceCall = x => Array . prototype . slice . call ( x ) ;
const firstChildInClass = ( parent , className ) => parent . getElementsByClassName ( className ) [ 0 ] ;
const createAndAssign = ( element , source ) => Object . assign ( document . createElement ( element ) , source ) ;
const createAndAssign = ( element , source ) => Object . assign ( document . createElement ( element ) , source ) ;
function addGlobalStyle ( css ) {
function addGlobalStyle ( css ) {
@ -70,7 +75,7 @@ function addGlobalStyle(css) {
function debug ( text ) {
function debug ( text ) {
if ( debugMode ) {
if ( debugMode ) {
console . log ( "[BantFlags] " + text ) ;
console . log ( '[BantFlags] ' + text ) ;
}
}
}
}
@ -85,27 +90,25 @@ function MakeRequest(method, url, data, func) {
url : url ,
url : url ,
data : data ,
data : data ,
headers : {
headers : {
"Content-Type" : "application/x-www-form-urlencoded"
"Content-Type" : 'application/x-www-form-urlencoded'
} ,
} ,
onload : func
onload : func
} ) ;
} ) ;
}
}
function retry ( func , resp ) {
function retry ( func , resp ) {
console . log ( "[BantFlags] Could not fetch flags, status: " + resp . status ) ;
console . log ( '[BantFlags] Could not fetch flags, status: ' + resp . status ) ;
console . log ( resp . statusText ) ;
console . log ( resp . statusText ) ;
setTimeout ( func , requestRetryInterval ) ;
setTimeout ( func , requestRetryInterval ) ;
}
}
/** nSetup, preferences */
/** nSetup, preferences */
// TODO: this shouldn't be a class .
// TODO: this shouldn't be a object .
var nsetup = { // not anymore a clone of the original setup
var nsetup = { // not anymore a clone of the original setup
namespace : 'BintFlegs' , // TODO: should be const.
namespace : 'BintFlegs' , // TODO: should be const.
flagsLoaded : false ,
flagsLoaded : false ,
form : "<span id=\"bantflags_container\"></span>" +
form : '<span id="bantflags_container"></span><button type="button" id="append_flag_button" title="Click to add selected flag to your flags. Click on flags to remove them. Saving happens automatically, you only need to refresh the pages that have an outdated flaglist on the page."><<</button><button id="flagLoad" type="button">Click to load flags.</button><select id="flagSelect"></select>' ,
"<button type=\"button\" id=\"append_flag_button\" title=\"Click to add selected flag to your flags. Click on flags to remove them. Saving happens automatically, you only need to refresh the pages that have an outdated flaglist on the page.\"><<</button>" +
"<button id=\"flagLoad\" type=\"button\">Click to load flags.</button><select id=\"flagSelect\"></select>" ,
fillHtml : function ( ) { // TODO: this function should have a better name. Only called by nsetup.init, can be inlined?
fillHtml : function ( ) { // TODO: this function should have a better name. Only called by nsetup.init, can be inlined?
// resolve flags
// resolve flags
@ -120,7 +123,7 @@ var nsetup = { // not anymore a clone of the original setup
return ;
return ;
}
}
let flagSelect = document . getElementById ( "flagSelect" ) ;
let flagSelect = document . getElementById ( 'flagSelect' ) ;
let flagLoad = document . getElementById ( 'flagLoad' ) ;
let flagLoad = document . getElementById ( 'flagLoad' ) ;
let flagsSupported = resp . responseText . split ( '\n' ) ;
let flagsSupported = resp . responseText . split ( '\n' ) ;
@ -128,7 +131,7 @@ var nsetup = { // not anymore a clone of the original setup
let flag = flagsSupported [ i ] ;
let flag = flagsSupported [ i ] ;
flagSelect . appendChild ( createAndAssign ( 'option' , {
flagSelect . appendChild ( createAndAssign ( 'option' , {
value : flag ,
value : flag ,
innerHTML : "<img src=\"" + back _end + flag _dir + flag + ".png\"" + " title=\"" + flag + "\">" + " " + flag
innerHTML : '<img src="' + back _end + flag _dir + flag + '.png" title="' + flag + '"> ' + flag
} ) ) ;
} ) ) ;
}
}
@ -143,12 +146,12 @@ var nsetup = { // not anymore a clone of the original setup
} ,
} ,
setFlag : function ( flag ) {
setFlag : function ( flag ) {
let UID = Math . random ( ) . toString ( 36 ) . substring ( 7 ) ;
let UID = Math . random ( ) . toString ( 36 ) . substring ( 7 ) ;
let flagName = flag ? flag : document . getElementById ( "flagSelect" ) . value ;
let flagName = flag ? flag : document . getElementById ( 'flagSelect' ) . value ;
let flagContainer = document . getElementById ( "bantflags_container" ) ;
let flagContainer = document . getElementById ( 'bantflags_container' ) ;
flagContainer . appendChild ( createAndAssign ( 'img' , {
flagContainer . appendChild ( createAndAssign ( 'img' , {
title : flagName ,
title : flagName ,
src : back _end + flag _dir + flagName + ".png" ,
src : back _end + flag _dir + flagName + '.png' ,
id : UID ,
id : UID ,
className : 'bantflags_flag'
className : 'bantflags_flag'
} ) ) ;
} ) ) ;
@ -158,13 +161,11 @@ var nsetup = { // not anymore a clone of the original setup
}
}
document . getElementById ( UID ) . addEventListener ( "click" , function ( ) {
document . getElementById ( UID ) . addEventListener ( "click" , function ( ) {
console . log ( "removing flag" ) ;
let flagToRemove = document . getElementById ( UID ) ;
let flagToRemove = document . getElementById ( UID ) ;
flagToRemove . parentNode . removeChild ( flagToRemove ) ;
flagToRemove . parentNode . removeChild ( flagToRemove ) ;
nsetup . toggleFlagButton ( 'on' ) ;
nsetup . toggleFlagButton ( 'on' ) ;
nsetup . save ( nsetup . parse ( ) ) ;
nsetup . save ( nsetup . parse ( ) ) ;
console . log ( "flag removed" ) ;
} ) ;
} ) ;
if ( ! flag ) {
if ( ! flag ) {
@ -175,15 +176,22 @@ var nsetup = { // not anymore a clone of the original setup
init : function ( ) {
init : function ( ) {
// here we insert the form for placing flags. How?
// here we insert the form for placing flags. How?
let flagsForm = createAndAssign ( "div" , {
let flagsForm = createAndAssign ( 'div' , {
className : 'flagsForm' ,
className : 'flagsForm' ,
innerHTML : nsetup . form
innerHTML : nsetup . form
} ) ;
} ) ;
addGlobalStyle ( '.flagsForm{float: right; clear: right; margin: 20px 10px;} #flagSelect{display:none;}' ) ;
addGlobalStyle ( '.flagsForm{float: right; clear: right; margin: 20px 10px;} #flagSelect{display:none;}' ) ;
addGlobalStyle ( ".bantflags_flag { padding: 1px;} [title^='Romania' ] { position: relative; animation: shakeAnim 0.1s linear infinite;} @keyframes shakeAnim { 0% {left: 1px;} 25% {top: 2px;} 50% {left: 1px;} 75% {left: 0px;} 100% {left: 2px;}}" ) ;
addGlobalStyle ( '.bantflags_flag { padding: 1px;} [title^="Romania" ] { position: relative; animation: shakeAnim 0.1s linear infinite;} @keyframes shakeAnim { 0% {left: 1px;} 25% {top: 2px;} 50% {left: 1px;} 75% {left: 0px;} 100% {left: 2px;}}' ) ;
firstChildInClass ( document , 'bottomCtrl' ) . parentNode . appendChild ( flagsForm ) ;
if ( site . fourchan ) {
document . getElementById ( 'delform' ) . appendChild ( flagsForm ) ;
}
// nineball and we're not in the index
if ( site . nineball && document . querySelector ( 'threads .pagination' ) === null ) {
document . querySelector ( 'threads section' ) . append ( flagsForm ) ;
}
for ( var i in regions ) {
for ( var i in regions ) {
nsetup . setFlag ( regions [ i ] ) ;
nsetup . setFlag ( regions [ i ] ) ;
@ -196,7 +204,7 @@ var nsetup = { // not anymore a clone of the original setup
} ,
} ,
parse : function ( ) {
parse : function ( ) {
let flagsArray = [ ] ;
let flagsArray = [ ] ;
let flagElements = elementsInClass ( "bantflags_flag" ) ;
let flagElements = document . getElementsByClassName ( "bantflags_flag" ) ;
for ( var i = 0 ; i < flagElements . length ; i ++ ) {
for ( var i = 0 ; i < flagElements . length ; i ++ ) {
flagsArray [ i ] = flagElements [ i ] . title ;
flagsArray [ i ] = flagElements [ i ] . title ;
@ -212,68 +220,64 @@ regions = GM_getValue(nsetup.namespace); // TODO: move this to other init stuff
if ( ! regions ) {
if ( ! regions ) {
regions = [ ] ;
regions = [ ] ;
setTimeout ( function ( ) {
setTimeout ( function ( ) {
window . confirm ( "Bant Flags: No Flags detected" ) ;
window . confirm ( 'Bant Flags: No Flags detected' ) ;
} , 2000 ) ;
} , 2000 ) ;
}
}
/** parse the posts already on the page before thread updater kicks in */
/** parse the posts already on the page before thread updater kicks in */
function parse4chanPosts ( ) {
function parse4chanPosts ( ) {
let posts = sliceCall ( elementsInClass ( 'postContainer' ) ) ;
let posts = document . querySelectorAll ( '.postContainer' ) ;
for ( var i = 0 ; i < posts . length ; i ++ ) {
for ( var i = 0 ; i < posts . length ; i ++ ) {
let postNumber = posts [ i ] . id . replace ( "pc" , "" ) ;
let postNumber = posts [ i ] . id . replace ( 'pc' , '' ) ; // Fuck you 4chan
postNrs . push ( postNumber ) ;
postNrs . push ( postNumber ) ;
}
}
debug ( postNrs ) ;
debug ( postNrs ) ;
}
}
function parseFoolFuukaPosts ( ) {
function getposts ( selector ) {
let nums = x => x . filter ( x => x . id !== '' ) . map ( x => x . id ) ;
let posts = document . querySelectorAll ( selector ) ;
let getPostNumbers = x => nums ( sliceCall ( elementsInClass ( x ) ) ) ;
postNrs = getPostNumbers ( 'thread' ) . concat ( getPostNumbers ( 'post' ) ) ;
for ( var i = 0 ; i < posts . length ; i ++ ) {
postNrs . push ( posts [ i ] . id ) ;
}
debug ( postNrs ) ;
debug ( postNrs ) ;
}
}
function onFlagsLoad ( response ) {
function onFlagsLoad ( response ) {
// because we only care about the end result, not how we got there.
// grandparent -> first parent -> first child.
let hopHTML = ( post _nr , first , second ) =>
firstChildInClass ( firstChildInClass ( document . getElementById ( post _nr ) , first ) , second ) ;
let MakeFlag = ( flag ) =>
let MakeFlag = ( flag ) =>
createAndAssign ( 'a' , {
createAndAssign ( 'a' , {
innerHTML : "<img src=\"" + back _end + flag _dir + flag + ".png\" title=\"" + flag + "\">" ,
innerHTML : '<img src="' + back _end + flag _dir + flag + '.png" title="' + flag + '"> ' ,
className : "bantFlag" ,
className : 'bantFlag' ,
target : "_blank"
target : '_blank'
} ) ;
} ) ;
debug ( "JSON: " + response . responseText ) ;
debug ( 'JSON: ' + response . responseText ) ;
var jsonData = JSON . parse ( response . responseText ) ;
var jsonData = JSON . parse ( response . responseText ) ;
Object . keys ( jsonData ) . forEach ( function ( post ) {
Object . keys ( jsonData ) . forEach ( function ( post ) {
let flagContainer = is _archive
var flagContainer ;
? hopHTML ( post , "post_data" , "post_type" )
if ( site . nineball ) { flagContainer = document . querySelector ( '[id="' + post + '"] header' ) ; }
: hopHTML ( "pc" + post , "postInfo" , "nameBlock" ) ;
if ( site . fourchan ) { flagContainer = document . querySelector ( '[id="pc' + post + '"] .postInfo .nameBlock' ) ; }
let currentFlag = firstChildInClass ( flagContainer , 'flag' ) ;
if ( site . foolfuuka ) { flagContainer = document . querySelector ( '[id="' + post + '"] .post_data .post_type' ) ; }
let flags = jsonData [ post ] ;
// If we have a bantflag and the original post has a flag
let flags = jsonData [ post ] ;
if ( flags . length > 0 && currentFlag !== undefined ) {
if ( flags . length > 0 ) {
console . log ( "[BantFlags] Resolving flags for >>" + post ) ;
console . log ( '[BantFlags] Resolving flags for >>' + post ) ;
for ( var i = 0 ; i < flags . length ; i ++ ) {
for ( var i = 0 ; i < flags . length ; i ++ ) {
let flag = flags [ i ] ;
let flag = flags [ i ] ;
let newFlag = MakeFlag ( flag ) ;
let newFlag = MakeFlag ( flag ) ;
if ( is _archive ) {
if ( site . foolfuuka ) {
newFlag . style = "padding: 0px 0px 0px " + ( 3 + 2 * ( i > 0 ) ) + "px; vertical-align:;display: inline-block; width: 16px; height: 11px; position: relative;" ;
newFlag . style = 'padding: 0px 0px 0px ' + ( 3 + 2 * ( i > 0 ) ) + 'px; vertical-align:;display: inline-block; width: 16px; height: 11px; position: relative;' ;
}
if ( site . nineball ) {
newFlag . title = flag ;
}
}
flagContainer . append ( newFlag ) ;
flagContainer . append ( newFlag ) ;
console . log ( "\t -> " + flag ) ;
console . log ( '\t -> ' + flag ) ;
}
}
}
}
} ) ;
} ) ;
@ -281,11 +285,13 @@ function onFlagsLoad(response) {
postNrs = [ ] ;
postNrs = [ ] ;
}
}
/** Gets flags from the database. */
function resolveRefFlags ( ) {
function resolveRefFlags ( ) {
debug ( 'Board is: ' + board _id ) ;
MakeRequest (
MakeRequest (
"POST" ,
'POST' ,
back _end + api _get ,
back _end + api _get ,
"post_nrs=" + encodeURIComponent ( postNrs ) + "&board=" + encodeURIComponent ( boardID ) + "&version=" + encodeURIComponent ( version ) ,
'post_nrs=' + encodeURIComponent ( postNrs ) + '&board=' + encodeURIComponent ( board _id ) + '&version=' + encodeURIComponent ( version ) ,
function ( resp ) {
function ( resp ) {
if ( resp . status !== 200 ) {
if ( resp . status !== 200 ) {
retry ( resolveRefFlags , resp ) ;
retry ( resolveRefFlags , resp ) ;
@ -297,26 +303,39 @@ function resolveRefFlags() {
}
}
// Flags need to be parsed and aligned differently between boards.
// Flags need to be parsed and aligned differently between boards.
if ( is _archive ) {
if ( site . fourchan ) {
debug ( "FoolFuuka." ) ;
debug ( '4chan' ) ;
parseFoolFuukaPosts ( ) ;
board _id = 'bant' ;
addGlobalStyle ( '.bantFlag{top: -2px !important;left: -1px !important}' ) ;
}
else {
debug ( "4chan." ) ;
parse4chanPosts ( ) ;
parse4chanPosts ( ) ;
addGlobalStyle ( '.flag{top: 0px !important;left: -1px !important}' ) ;
addGlobalStyle ( '.flag{top: 0px !important;left: -1px !important}' ) ;
addGlobalStyle ( ".bantFlag {padding: 0px 0px 0px 5px; vertical-align:;display: inline-block; width: 16px; height: 11px; position: relative;}" ) ;
addGlobalStyle ( '.bantFlag {padding: 0px 0px 0px 5px; vertical-align:;display: inline-block; width: 16px; height: 11px; position: relative;}' ) ;
}
if ( site . nineball ) {
debug ( regions ) ;
debug ( 'Nineball' ) ;
board _id = window . location . pathname . split ( '/' ) [ 1 ] ; // 'nap' or 'srsbsn'
getposts ( 'section[id], article[id]' ) ;
addGlobalStyle ( '.bantFlag {cursor: default} .bantFlag img {pointer-events: none;}' ) ;
}
}
resolveRefFlags ( ) ; // Get flags from db.
if ( site . foolfuuka ) { // Archive.
debug ( 'FoolFuuka' ) ;
board _id = 'bant' ;
getposts ( 'article[id]' ) ;
addGlobalStyle ( '.bantFlag{top: -2px !important;left: -1px !important}' ) ;
}
if ( ! is _archive ) {
resolveRefFlags ( ) ; // Get flags from DB.
// Posting new flags and getting flags as posts are added to the thread.
if ( site . fourchan ) {
let GetEvDetail = e => e . detail || e . wrappedJSObject . detail ;
let GetEvDetail = e => e . detail || e . wrappedJSObject . detail ;
let method = "POST" ,
let method = 'POST' ,
url = back _end + api _post ,
url = back _end + api _post ,
func = function ( resp ) {
func = function ( resp ) {
debug ( resp . responseText ) ;
debug ( resp . responseText ) ;
@ -332,7 +351,7 @@ if (!is_archive) {
//setTimeout to support greasemonkey 1.x
//setTimeout to support greasemonkey 1.x
setTimeout ( function ( ) {
setTimeout ( function ( ) {
var data = "post_nr=" + encodeURIComponent ( e . detail . postID ) + "&board=" + encodeURIComponent ( e . detail . boardID ) + "®ions=" + encodeURIComponent ( regions ) + "&version=" + encodeURIComponent ( version ) ;
var data = 'post_nr=' + encodeURIComponent ( e . detail . postID ) + '&board=' + encodeURIComponent ( e . detail . boardID ) + '®ions=' + encodeURIComponent ( regions ) + '&version=' + encodeURIComponent ( version ) ;
MakeRequest ( method , url , data , func ) ;
MakeRequest ( method , url , data , func ) ;
} , 0 ) ;
} , 0 ) ;
} , false ) ;
} , false ) ;
@ -343,7 +362,7 @@ if (!is_archive) {
//setTimeout to support greasemonkey 1.x
//setTimeout to support greasemonkey 1.x
setTimeout ( function ( ) {
setTimeout ( function ( ) {
var data = "post_nr=" + encodeURIComponent ( evDetail . postId ) + "&board=" + encodeURIComponent ( boardID ) + "®ions=" + encodeURIComponent ( regions ) + "&version=" + encodeURIComponent ( version ) ;
var data = 'post_nr=' + encodeURIComponent ( evDetail . postId ) + '&board=' + encodeURIComponent ( board _id ) + '®ions=' + encodeURIComponent ( regions ) + '&version=' + encodeURIComponent ( version ) ;
MakeRequest ( method , url , data , func ) ;
MakeRequest ( method , url , data , func ) ;
} , 0 ) ;
} , 0 ) ;
} , false ) ;
} , false ) ;
@ -380,7 +399,7 @@ if (!is_archive) {
//add to temp posts and the DOM element to allPostsOnPage
//add to temp posts and the DOM element to allPostsOnPage
lastPosts . forEach ( function ( post _container ) {
lastPosts . forEach ( function ( post _container ) {
var post _nr = post _container . id . replace ( "pc" , "" ) ;
var post _nr = post _container . id . replace ( 'pc' , '' ) ;
postNrs . push ( post _nr ) ;
postNrs . push ( post _nr ) ;
} ) ;
} ) ;
@ -389,4 +408,27 @@ if (!is_archive) {
} , false ) ;
} , false ) ;
/** setup init and start first calls */
/** setup init and start first calls */
nsetup . init ( ) ;
nsetup . init ( ) ;
}
if ( site . nineball ) {
nsetup . init ( ) ;
new MutationObserver ( function ( mutations ) {
mutations . forEach ( function ( mutation ) {
if ( mutation . addedNodes [ 0 ] . nodeName == 'HEADER' ) { // When you make a post
let data = 'post_nr=' + encodeURIComponent ( mutation . target . id ) + '&board=' + encodeURIComponent ( board _id ) + '®ions=' + encodeURIComponent ( regions ) + '&version=' + encodeURIComponent ( version ) ;
MakeRequest (
'POST' ,
back _end + api _post ,
data ,
function ( resp ) {
postNrs . push ( mutation . target . id ) ;
setTimeout ( resolveRefFlags , 0 ) ;
} ) ;
}
if ( mutation . addedNodes [ 0 ] . nodeName == 'ARTICLE' ) { // When someone else makes a post
postNrs . push ( mutation . addedNodes [ 0 ] . id ) ;
setTimeout ( resolveRefFlags , 1500 ) ; // Wait 1.5s so the database can process the post, since they appear instantly.
}
} ) ;
} ) . observe ( document . querySelector ( 'threads' ) , { childList : true , subtree : true } ) ;
}
}