// ==UserScript==
// name and namespace cannot be changed - it would break the update mechanism, that's why we will leave the name at Extra Flags for int
// @name Extra Flags for int
// @namespace com.whatisthisimnotgoodwithcomputers.extraflagsforint
// @description Extra Flags for int v2 "City flags were a mistake" edition
// @include http*://boards.4chan.org/int/*
// @include http*://boards.4chan.org/sp/*
// @include http*://boards.4chan.org/pol/*
// @include http*://boards.4chan.org/bant/*
// @exclude http*://boards.4chan.org/int/catalog
// @exclude http*://boards.4chan.org/sp/catalog
// @exclude http*://boards.4chan.org/pol/catalog
// @exclude http*://boards.4chan.org/bant/catalog
// @version 0.29
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
// DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR REGION SHOULD BE CONFIGURED BY USING THE CONFIGURATION BOXES (see install webms for help)
/** JSLint excludes */
/*jslint browser: true*/
/*global document, console, GM_addStyle, GM_setValue, GM_getValue, GM_registerMenuCommand, GM_xmlhttpRequest, cloneInto, unsafeWindow*/
/* WebStorm JSLint ticked:
- uncapitalized constructors
- missing 'use strict' pragma
- many var statements
*/
/* Right margin: 160 */
// DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR REGION SHOULD BE CONFIGURED BY USING THE CONFIGURATION BOXES (see install webms for help)
var regions = [];
var radio = "all";
var lastRegion = ""; //used for back button
var regionVariable = 'regionVariableAPI2';
var radioVariable = 'radioVariableAPI2';
var allPostsOnPage = [];
var postNrs = [];
var postRemoveCounter = 60;
var requestRetryInterval = 5000;
var flegsBaseUrl = 'https://raw.githubusercontent.com/flaghunters/Extra-Flags-for-int-/master/flags/';
// remove comment and change link to add country flag icons into selection menu var countryFlegsBaseUrl = 'https://raw.githubusercontent.com/flagzzzz/Extra-Flags-for-4chan/master/flags/';
var flagListFile = 'flag_list.txt';
var backendBaseUrl = 'https://whatisthisimnotgoodwithcomputers.com/';
var postUrl = 'int/post_flag_api2.php';
var getUrl = 'int/get_flags_api2.php';
var shortId = 'witingwc.ef.';
var regionDivider = "||";
/** Setup, preferences */
var setup = {
namespace: 'com.whatisthisimnotgoodwithcomputers.extraflagsforint.',
id: "ExtraFlags-setup",
html: function () {
var htmlFixedStart = '<div>Extra Flags for 4chan v2</div><br/>';
var htmlBackButton = '<button name="back">Back</button>';
var htmlNextButton = '<button name="forward">Next</button>';
var htmlBackNextButtons = '<div>' + htmlBackButton + htmlNextButton + '</div>';
var htmlSaveButton = '<div><button name="save" title="Pressing "Save Regions" will set your regions to the ones current displayed below.">' +
'Save Regions</button></div><br/>';
var htmlHelpText = '<label name="' + shortId + 'label"> You can go as deep as you like, regions stack.<br/>' +
'For example; United States, California, Los Angeles<br/></label>' +
'<label>Country must match your flag! Your flag not here? Open issue here:<br/>' +
'<a href="https://github.com/flaghunters/Extra-Flags-for-4chan/issues" style="color:blue">' +
'https://github.com/flaghunters/Extra-Flags-for-4chan/issues</a></label>';
var filterRadio = '<br/><br/><form id="filterRadio">' +
'<input type="radio" name="filterRadio" id="filterRadioall" style="display: inline !important;" value="all"><label>Show country + ALL regions.</label>' +
'<br/><input type="radio" name="filterRadio" id="filterRadiofirst" style="display: inline !important;" value="first"><label>Only show country + FIRST region.</label>' +
'<br/><input type="radio" name="filterRadio" id="filterRadiolast" style="display: inline !important;" value="last"><label>Only show country + LAST region. (v1/old format)</label>' +
'</form>';
if (regions.length > 1) {
var selectMenuFlags = "Regional flags selected: ";
var path = flegsBaseUrl + "/" + regions[0];
for (var i = 1; i < regions.length; i++) {
path += "/" + regions[i];
selectMenuFlags += "<img src='" + path + ".png'" + " title='" + regions[i] + "'> ";
}
selectMenuFlags += "<br/>";
return htmlFixedStart + '<div>Region: <br/><select id="' + shortId + 'countrySelect">' +
'</select></div><br/>' + htmlBackNextButtons +
'<br/>' + htmlSaveButton + '</div>' + selectMenuFlags + htmlHelpText + filterRadio;
}
if (regions.length == 1) {
var selectMenuFlags = "<br/>";
return htmlFixedStart + '<div>Region: <br/><select id="' + shortId + 'countrySelect">' +
'</select></div><br/>' + htmlBackNextButtons +
'<br/>' + '</div><br/><br/>' + selectMenuFlags + htmlHelpText + filterRadio;
}
return htmlFixedStart + '<div>Country: <br/><select id="' + shortId + 'countrySelect">' +
'</select></div><br/>' + htmlBackNextButtons + '<br/>' + htmlHelpText + filterRadio;
},
fillHtml: function (path1) {
if (path1 === "") { //normal call
var path = flegsBaseUrl + "/";
var oldPath = path;
if (regions.length > 0) {
for (var i = 0; i < regions.length; i++) {
oldPath = path;
path += regions[i] + "/";
}
}
var pathNoFlagList = path;
} else { // end of folder line call
path = path1;
oldPath = "";
var pathNoFlagList = path;
}
/* resolve countries which we support */
GM_xmlhttpRequest({
method: "GET",
url: path + flagListFile,
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
onload: function (response) {
if (response.status == 404) { // detect if there are no more folders
setup.fillHtml(oldPath);
setup.q('forward').disabled = true; // disable next button
} else {
//hide spam, debug purposes only
//console.log(response.responseText);
var countrySelect = document.getElementById(shortId + 'countrySelect'),
countriesAvailable = response.responseText.split('\n');
for (var countriesCounter = 0; countriesCounter < countriesAvailable.length - 1; countriesCounter++) {
var opt = document.createElement('option');
opt.value = countriesAvailable[countriesCounter];
if (regions.length > 0) {
opt.innerHTML = countriesAvailable[countriesCounter] + " " + "<img src=\"" + flegsBaseUrl + pathNoFlagList + countriesAvailable[countriesCounter] + ".png\"" + " title=\"" + countriesAvailable[countriesCounter] + "\">";
} else {
opt.innerHTML = countriesAvailable[countriesCounter]; // remove comment to enable country flags in the selection menu + " " + "<img src=\"" + countryFlegsBaseUrl + countriesAvailable[countriesCounter] + ".png\"" + " title=\"" + countriesAvailable[countriesCounter] + "\">";
}
if (lastRegion != "" && countriesAvailable[countriesCounter] === lastRegion) { // automatically select last selected when going up a folder
opt.selected = "selected";
} else if (oldPath == "" && countriesAvailable[countriesCounter] === regions[regions.length - 1]) { // show final selected when no more
// folders detected
opt.selected = "selected";
}
countrySelect.appendChild(opt);
}
}
}
});
},
setRadio: function() {
var radioStatus = setup.load(radioVariable);
if (!radioStatus || radioStatus === "" || radioStatus === "undefined") {
radioStatus = "all";
}
var radioButton = document.getElementById("filterRadio" + radioStatus);
radioButton.checked = true;
},
q: function (n) {
return document.querySelector('#' + this.id + ' *[name="' + n + '"]');
},
removeExtra: function () {
if (regions.length > 0) {
lastRegion = regions[regions.length - 1];
regions.pop();
}
setup.show();
},
show: function () {
/* remove setup window if existing */
var setup_el = document.getElementById(setup.id);
if (setup_el) {
setup_el.parentNode.removeChild(setup_el);
}
/* create new setup window */
GM_addStyle('\
#' + setup.id + ' { position:fixed;z-index:10001;top:40px;right:40px;padding:20px 30px;background-color:white;width:auto;border:1px solid black }\
#' + setup.id + ' * { color:black;text-align:left;line-height:normal;font-size:12px }\
#' + setup.id + ' div { text-align:center;font-weight:bold;font-size:14px }'
);
setup_el = document.createElement('div');
setup_el.id = setup.id;
setup_el.innerHTML = setup.html();
setup.fillHtml("", "");
document.body.appendChild(setup_el);
setup.setRadio();
/* button listeners */
setup.q('back').addEventListener('click', function () {
if (regions.length > 0) {
if (setup.q('forward').disabled == true) {
setup.q('forward').disabled = false; // reenable next button
}
lastRegion = regions[regions.length - 1];
regions.pop();
setup.show();
}
}, false);
setup.q('forward').addEventListener('click', function () {
var e = document.getElementById(shortId + "countrySelect");
var temp = e.options[e.selectedIndex].value;
lastRegion = "";
if (temp != "" && regions[regions.length - 1] != temp) {
this.disabled = true;
this.innerHTML = 'Saving...';
lastRegion = regions[regions.length - 1];
regions.push(temp);
setup.show();
}
}, false);
setup.q('save').addEventListener('click', function () {
var e = document.getElementById(shortId + "countrySelect");
var temp = e.options[e.selectedIndex].value;
if (regions[regions.length - 1] === "") { //prevent last spot from being blank
regions.pop();
}
lastRegion = "";
radio = document.querySelector('input[name="filterRadio"]:checked').value;
setup.save(radioVariable, radio);
alert('Flags set: ' + regions + '\n\n' + 'Refresh all your 4chan tabs and be sure to post using the quick reply window!');
this.disabled = true;
this.innerHTML = 'Saving...';
setup_el.parentNode.removeChild(setup_el);
setup.save(regionVariable, regions);
}, false);
},
save: function (k, v) {
GM_setValue(setup.namespace + k, v);
},
load: function (k) {
return GM_getValue(setup.namespace + k);
},
init: function () {
//GM_registerMenuCommand('Extra Flags setup', setup.show;
GM_registerMenuCommand('Extra Flags setup', setup.show);
}
};
/** Prompt to set region if regionVariable is empty */
regions = setup.load(regionVariable);
radio = setup.load(radioVariable);
if (!regions) {
regions = [];
setTimeout(function () {
if (window.confirm("Extra Flags: No region detected, set it up now?") === true) {
setup.show();
}
}, 2000);
}
if (!radio || radio === "" || radio === "undefined") {
radio = "all";
}
/** parse the posts already on the page before thread updater kicks in */
function parseOriginalPosts() {
var tempAllPostsOnPage = document.getElementsByClassName('postContainer');
allPostsOnPage = Array.prototype.slice.call(tempAllPostsOnPage); //convert from element list to javascript array
postNrs = allPostsOnPage.map(function (p) {
return p.id.replace("pc", "");
});
}
/** the function to get the flags from the db uses postNrs
* member variable might not be very nice but it's the easiest approach here */
function onFlagsLoad(response) {
//exit on error
if (response.status !== 200) {
console.log("Could not fetch flags, status: " + response.status);
console.log(response.statusText);
setTimeout(resolveRefFlags, requestRetryInterval);
return;
}
var jsonData = JSON.parse(response.responseText);
jsonData.forEach(function (post) {
var postToAddFlagTo = document.getElementById("pc" + post.post_nr),
postInfo = postToAddFlagTo.getElementsByClassName('postInfo')[0],
nameBlock = postInfo.getElementsByClassName('nameBlock')[0],
currentFlag = nameBlock.getElementsByClassName('flag')[0],
postedRegions = post.region.split(regionDivider);
if (postedRegions.length > 0 && !(currentFlag === undefined)) {
var path = currentFlag.title;
for (var i = 0; i < postedRegions.length; i++) {
path += "/" + postedRegions[i];
// this is probably quite a dirty fix, but it's fast
if ((radio === "all") || (radio === "first" && i === 0) || (radio === "last" && i === (postedRegions.length - 1))) {
var newFlag = document.createElement('a');
nameBlock.appendChild(newFlag);
var lastI = i;
if (radio === 'last') {
lastI = 0;
}
var newFlagImgOpts = 'onerror="(function () {var extraFlagsImgEl = document.getElementById(\'pc' + post.post_nr +
'\').getElementsByClassName(\'extraFlag\')[' + lastI +
'].firstElementChild; if (!/\\/empty\\.png$/.test(extraFlagsImgEl.src)) {extraFlagsImgEl.src = \'' +
flegsBaseUrl + 'empty.png\';}})();"';
newFlag.innerHTML = "<img src=\"" + flegsBaseUrl + path + ".png\"" + newFlagImgOpts + " title=\"" + postedRegions[i] + "\">";
newFlag.className = "extraFlag";
if (i > 0) {
newFlag.href = "https://www.google.com/search?q=" + postedRegions[i] + ", " + postedRegions[i - 1];
} else {
newFlag.href = "https://www.google.com/search?q=" + postedRegions[i] + ", " + currentFlag.title;
}
newFlag.target = '_blank';
//padding format: TOP x RIGHT_OF x BOTTOM x LEFT_OF
newFlag.style = "padding: 0px 0px 0px 5px; vertical-align:;display: inline-block; width: 16px; height: 11px; position: relative;";
console.log("resolved " + postedRegions[i]);
}
}
}
//postNrs are resolved and should be removed from this variable
var index = postNrs.indexOf(post.post_nr);
if (index > -1) {
postNrs.splice(index, 1);
}
});
//removing posts older than the time limit (they likely won't resolve)
var timestampMinusPostRemoveCounter = Math.round(+new Date() / 1000) - postRemoveCounter;
postNrs.forEach(function (post_nr) {
var postToAddFlagTo = document.getElementById("pc" + post_nr),
postInfo = postToAddFlagTo.getElementsByClassName('postInfo')[0],
dateTime = postInfo.getElementsByClassName('dateTime')[0];
if (dateTime.getAttribute("data-utc") < timestampMinusPostRemoveCounter) {
var index = postNrs.indexOf(post_nr);
if (index > -1) {
postNrs.splice(index, 1);
}
}
});
}
/** fetch flags from db */
function resolveRefFlags() {
var boardID = window.location.pathname.split('/')[1];
if (boardID === "int" || boardID === "sp" || boardID === "pol" || boardID === "bant") {
GM_xmlhttpRequest({
method: "POST",
url: backendBaseUrl + getUrl,
data: "post_nrs=" + encodeURIComponent(postNrs) + "&" + "board=" + encodeURIComponent(boardID),
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
onload: onFlagsLoad
});
}
}
/** send flag to system on 4chan x (v2, loadletter, v3 untested) post
* handy comment to save by ccd0
* console.log(e.detail.boardID); // board name (string)
* console.log(e.detail.threadID); // thread number (integer in ccd0, string in loadletter)
* console.log(e.detail.postID); // post number (integer in ccd0, string in loadletter) */
document.addEventListener('QRPostSuccessful', function (e) {
//setTimeout to support greasemonkey 1.x
setTimeout(function () {
GM_xmlhttpRequest({
method: "POST",
url: backendBaseUrl + postUrl,
data: "post_nr=" + encodeURIComponent(e.detail.postID) + "&" + "board=" + encodeURIComponent(e.detail.boardID) + "&" + "regions=" +
encodeURIComponent(regions.slice(1).join(regionDivider)),
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
onload: function (response) {
//hide spam, debug purposes only
//console.log(response.responseText);
}
});
}, 0);
}, false);
/** send flag to system on 4chan inline post */
document.addEventListener('4chanQRPostSuccess', function (e) {
var boardID = window.location.pathname.split('/')[1];
var evDetail = e.detail || e.wrappedJSObject.detail;
//setTimeout to support greasemonkey 1.x
setTimeout(function () {
GM_xmlhttpRequest({
method: "POST",
url: backendBaseUrl + postUrl,
data: "post_nr=" + encodeURIComponent(evDetail.postId) + "&" + "board=" + encodeURIComponent(boardID) + "&" + "regions=" +
encodeURIComponent(regions.slice(1).join(regionDivider)),
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
onload: function (response) {
//hide spam, debug only
//console.log(response.responseText);
}
});
}, 0);
}, false);
/** Listen to post updates from the thread updater for 4chan x v2 (loadletter) and v3 (ccd0 + ?) */
document.addEventListener('ThreadUpdate', function (e) {
var evDetail = e.detail || e.wrappedJSObject.detail;
var evDetailClone = typeof cloneInto === 'function' ? cloneInto(evDetail, unsafeWindow) : evDetail;
//ignore if 404 event
if (evDetail[404] === true) {
return;
}
setTimeout(function () {
//add to temp posts and the DOM element to allPostsOnPage
evDetailClone.newPosts.forEach(function (post_board_nr) {
var post_nr = post_board_nr.split('.')[1];
postNrs.push(post_nr);
var newPostDomElement = document.getElementById("pc" + post_nr);
allPostsOnPage.push(newPostDomElement);
});
}, 0);
//setTimeout to support greasemonkey 1.x
setTimeout(resolveRefFlags, 0);
}, false);
/** Listen to post updates from the thread updater for inline extension */
document.addEventListener('4chanThreadUpdated', function (e) {
var evDetail = e.detail || e.wrappedJSObject.detail;
var threadID = window.location.pathname.split('/')[3];
var postsContainer = Array.prototype.slice.call(document.getElementById('t' + threadID).childNodes);
var lastPosts = postsContainer.slice(Math.max(postsContainer.length - evDetail.count, 1)); //get the last n elements (where n is evDetail.count)
//add to temp posts and the DOM element to allPostsOnPage
lastPosts.forEach(function (post_container) {
var post_nr = post_container.id.replace("pc", "");
postNrs.push(post_nr);
allPostsOnPage.push(post_container);
});
//setTimeout to support greasemonkey 1.x
setTimeout(resolveRefFlags, 0);
}, false);
/** START fix flag alignment on chrome */
function addGlobalStyle(css) {
var head, style;
head = document.getElementsByTagName('head')[0];
if (!head) {
return;
}
style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = css;
head.appendChild(style);
}
if (navigator.userAgent.toLowerCase().indexOf('webkit') > -1) {
addGlobalStyle('.flag{top: 0px !important;left: -1px !important}');
}
/** END fix flag alignment on chrome */
/** setup init and start first calls */
setup.init();
parseOriginalPosts();
resolveRefFlags();