Minor code cleanup.

dotnetflags
C-xC-c 5 years ago
parent 184293b5e8
commit 5ec1629637

@ -38,7 +38,7 @@ namespace BantFlags.Controllers
if (ver > 1) if (ver > 1)
{ {
// Improved data structuring, see Docs/GetPosts // Improved data structuring, see Docs/get
return Json(await Database.GetPosts_V2(post_nrs, board)); return Json(await Database.GetPosts_V2(post_nrs, board));
} }

@ -39,7 +39,6 @@ namespace BantFlags.Data
{ {
return new PoolObject<MySqlConnection>(await Connections.TakeAsync(), obj => return new PoolObject<MySqlConnection>(await Connections.TakeAsync(), obj =>
{ {
// TODO: Why can't I use EnsureConnectionIsOpen() here?
if (obj.State != ConnectionState.Open) if (obj.State != ConnectionState.Open)
{ {
obj.Open(); obj.Open();

@ -2,7 +2,6 @@
// This file is part of BantFlags. // This file is part of BantFlags.
// BantFlags is licensed under the GNU AGPL Version 3.0 or later. // BantFlags is licensed under the GNU AGPL Version 3.0 or later.
// see the LICENSE file or <https://www.gnu.org/licenses/> // see the LICENSE file or <https://www.gnu.org/licenses/>
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;

@ -3,9 +3,6 @@
// BantFlags is licensed under the GNU AGPL Version 3.0 or later. // BantFlags is licensed under the GNU AGPL Version 3.0 or later.
// see the LICENSE file or <https://www.gnu.org/licenses/> // see the LICENSE file or <https://www.gnu.org/licenses/>
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BantFlags.Data namespace BantFlags.Data
{ {

@ -63,7 +63,6 @@ namespace BantFlags
} }
StagedFlags.Flags.Add(stagingFlag.Value); StagedFlags.Flags.Add(stagingFlag.Value);
StagedFlags.Names.Remove(stagingFlag.Value.OldName);
StagedFlags.Names.Add(stagingFlag.Value.Name); StagedFlags.Names.Add(stagingFlag.Value.Name);
Message = $"{stagingFlag.Value.OldName} renamed to {stagingFlag.Value.Name}."; Message = $"{stagingFlag.Value.OldName} renamed to {stagingFlag.Value.Name}.";
@ -136,7 +135,6 @@ namespace BantFlags
case Method.Rename: case Method.Rename:
StagedFlags.Names.Remove(flag.Name); StagedFlags.Names.Remove(flag.Name);
StagedFlags.Names.Add(flag.OldName);
break; break;
default: default:
@ -169,13 +167,7 @@ namespace BantFlags
case Method.Delete: case Method.Delete:
await Database.DeleteFlagAsync(flag); await Database.DeleteFlagAsync(flag);
Directory.Move(WebRoot + "/flags/" + flagname, WebRoot + "/flags/dead/" + Guid.NewGuid().ToString()); // Use a GUID so flags with the same name can be deleted.
if (System.IO.File.Exists(WebRoot + "/flags/dead/" + flagname))
{
System.IO.File.Delete(WebRoot + "/flags/dead/" + flagname); // TODO: This is not the right way to handle it.
}
Directory.Move(WebRoot + "/flags/" + flagname, WebRoot + "/flags/dead/" + flagname);
break; break;
case Method.Rename: case Method.Rename:

@ -13,7 +13,6 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
namespace BantFlags namespace BantFlags
{ {
@ -29,17 +28,18 @@ namespace BantFlags
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddControllers().AddNewtonsoftJson(); services.AddControllers().AddNewtonsoftJson(); // So we can format complex datatypes into JSON
services.AddRazorPages(); services.AddRazorPages();
// TODO: this shouldn't just be for Linux. if (!Directory.Exists(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "keys/")))
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) // For image upload during production.
{ {
Directory.CreateDirectory(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "keys/"));
}
services.AddDataProtection() services.AddDataProtection()
.SetApplicationName("BantFlags") .SetApplicationName("BantFlags")
.PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "keys"))); .PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "keys")));
}
services.AddSingleton(new DatabaseService(Configuration.GetSection("dbconfig").Get<DatabaseServiceConfig>())); services.AddSingleton(new DatabaseService(Configuration.GetSection("dbconfig").Get<DatabaseServiceConfig>()));
services.AddSingleton(new Staging(Configuration.GetValue<string>("staging-password"))); services.AddSingleton(new Staging(Configuration.GetValue<string>("staging-password")));

@ -11,7 +11,7 @@
// @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/
// @exclude http*://thebarchive.com/bant/statistics/ // @exclude http*://thebarchive.com/bant/statistics/
// @version 1.4.0 // @version 1.4.1
// @grant GM.xmlHttpRequest // @grant GM.xmlHttpRequest
// @grant GM.getValue // @grant GM.getValue
// @grant GM.setValue // @grant GM.setValue

@ -11,7 +11,7 @@
// @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/
// @exclude http*://thebarchive.com/bant/statistics/ // @exclude http*://thebarchive.com/bant/statistics/
// @version 1.4.0 // @version 1.4.1
// @grant GM.xmlHttpRequest // @grant GM.xmlHttpRequest
// @grant GM.getValue // @grant GM.getValue
// @grant GM.setValue // @grant GM.setValue
@ -27,7 +27,7 @@
// see the LICENSE file or <https://www.gnu.org/licenses/> // see the LICENSE file or <https://www.gnu.org/licenses/>
// Change this if you want verbose debuging information in the console. // Change this if you want verbose debuging information in the console.
const debugMode = true; const debugMode = false;
// For idiots using GM4. Does anyone else not support GM_ functions? // For idiots using GM4. Does anyone else not support GM_ functions?
if (typeof GM === 'undefined') { if (typeof GM === 'undefined') {
@ -40,21 +40,21 @@ if (typeof GM === 'undefined') {
// //
// DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR FLAGS SHOULD BE CONFIGURED USING THE FLAG SELECT // DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR FLAGS SHOULD BE CONFIGURED USING THE FLAG SELECT
// //
const postRemoveCounter = 60;
const requestRetryInterval = 5000; // TODO: maybe a max retries counter as well?
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';
const flag_dir = 'flags/'; const flag_dir = 'flags/';
const api_get = 'api/get'; const api_get = 'api/get';
const api_post = 'api/post'; const api_post = 'api/post';
const namespace = 'BintFlegs';
// If you increase this the server will ignore your post. // If you increase this the server will ignore your post.
const max_flags = 30; const max_flags = 30;
var regions = []; // The flags we have selected. let regions = []; // The flags we have selected.
var postNrs = []; // all post numbers in the thread. let postNrs = []; // all post numbers in the thread.
var board_id = ""; // The board we get flags for. let board_id = ""; // The board we get flags for.
let flagsLoaded = false;
// //
// DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR FLAGS SHOULD BE CONFIGURED USING THE FLAG SELECT // DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR FLAGS SHOULD BE CONFIGURED USING THE FLAG SELECT
// //
@ -66,15 +66,14 @@ const software = {
foolfuuka: document.querySelector('div[id="main"] article header .post_data') !== null foolfuuka: document.querySelector('div[id="main"] article header .post_data') !== null
}; };
// This is kinda necessary but I don't know how much. Can I just wrap checking regions on startup or is the whole script required? I won't be testing that for now.
(async () => {
/** Wrapper around Object.assign and document.createElement /** Wrapper around Object.assign and document.createElement
* @param {string} element - The HTML Element to create. * @param {string} element - The HTML Element to create.
* @param {Object} source - The properties to assign to the element. * @param {object} source - The properties to assign to the element.
* @returns {object} The HTML tag created */ * @returns {object} The HTML tag created */
const createAndAssign = (element, source) => Object.assign(document.createElement(element), source); const createAndAssign = (element, source) => Object.assign(document.createElement(element), source);
const toggleFlagButton = state => document.getElementById('append_flag_button').disabled = state === 'off' ? true : false;
/** Add a stylesheet to the head of the document. /** Add a stylesheet to the head of the document.
* @param {string} css - The CSS rules for the stylesheet. * @param {string} css - The CSS rules for the stylesheet.
* @returns {object} The style element appended to the head */ * @returns {object} The style element appended to the head */
@ -91,12 +90,12 @@ const software = {
} }
} }
/** Wrapper around GM.xmlhttpRequest. /** Wrapper around GM_xmlhttpRequest.
* @param {string} method - The HTTP method (GET, POST). * @param {string} method - The HTTP method (GET, POST).
* @param {string} url - The URL of the request. * @param {string} url - The URL of the request.
* @param {string} data - Data sent inn the form body. * @param {string} data - text for the form body.
* @param {Function} func - The function run when we recieve a response. Response data is sent directly to it. */ * @param {Function} func - The function run when we recieve a response. Response data is sent directly to it. */
const MakeRequest = ((method, url, data, func) => { const makeRequest = ((method, url, data, func) => {
GM.xmlHttpRequest({ GM.xmlHttpRequest({
method: method, method: method,
url: url, url: url,
@ -106,70 +105,21 @@ const software = {
}); });
}); });
/** Try some MakeRequest again if it fails. /** Itterate over selected flags are store them across browser sessions.*/
* @param {function} func - The function to retry. function saveFlags() {
* @param {XMLHttpRequest} resp - The XMLHttpResponse from the failed request.*/ regions = [];
function retry(func, resp) {
console.log('[BantFlags] Could not fetch flags, status: ' + resp.status);
console.log(resp.statusText);
setTimeout(func, requestRetryInterval);
}
// TODO: this shouldn't be a object.
var nsetup = { // not anymore a clone of the original setup
namespace: 'BintFlegs', // TODO: should be const.
flagsLoaded: false,
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><div id="flagSelect" ><ul class="hide"></ul><input type="button" value="(You)" onclick=""></div>',
fillHtml: function () { // TODO: this function should have a better name. Only called by nsetup.init, can be inlined?
MakeRequest(
"GET",
back_end + api_flags,
"version=" + encodeURIComponent(version),
function (resp) {
debug('Loading flags.');
if (resp.status !== 200) {
retry(nsetup.fillHtml, resp);
return;
}
let flagSelect = document.getElementById('flagSelect');
let flagList = flagSelect.querySelector('ul');
let flagInput = flagSelect.querySelector('input');
let flags = resp.responseText.split('\n');
for (var i = 0; i < flags.length; i++) {
let flag = flags[i];
flagList.appendChild(createAndAssign('li', {
innerHTML: '<img src="' + back_end + flag_dir + flag + '.png" title="' + flag + '"> <span>' + flag + '</span>'
}));
}
flagSelect.addEventListener('click', (e) => {
listItem = e.target.nodeName === 'LI' ? e.target : e.target.parentNode;
if (listItem.nodeName === 'LI') {
flagInput.value = listItem.querySelector('span').innerHTML;
}
flagList.classList.toggle('hide');
});
document.getElementById('flagLoad').style.display = 'none';
document.querySelector('.flagsForm').style.marginRight = "200px";
flagSelect.style.display = 'inline-block';
nsetup.flagsLoaded = true;
});
},
save: function () {
let storedFlags = [];
let selectedFlags = document.getElementsByClassName("bantflags_flag"); let selectedFlags = document.getElementsByClassName("bantflags_flag");
for (var i = 0; i < selectedFlags.length; i++) { for (var i = 0; i < selectedFlags.length; i++) {
storedFlags[i] = selectedFlags[i].title; regions[i] = selectedFlags[i].title;
} }
GM.setValue(nsetup.namespace, storedFlags); GM.setValue(namespace, regions);
regions = GM.getValue(nsetup.namespace); }
},
setFlag: function (flag) { /** Add a flag to our selection.
* @param {string} flag - The flag to add to our selection. If no value is passed it takes the current value from the flagSelect. */
function setFlag(flag) {
let UID = Math.random().toString(36).substring(7); let UID = Math.random().toString(36).substring(7);
let flagName = flag ? flag : document.querySelector('#flagSelect input').value; let flagName = flag ? flag : document.querySelector('#flagSelect input').value;
let flagContainer = document.getElementById('bantflags_container'); let flagContainer = document.getElementById('bantflags_container');
@ -182,23 +132,25 @@ const software = {
})); }));
if (flagContainer.children.length >= max_flags) { if (flagContainer.children.length >= max_flags) {
nsetup.toggleFlagButton('off'); toggleFlagButton('off');
} }
document.getElementById(UID).addEventListener("click", (e) => { document.getElementById(UID).addEventListener("click", (e) => {
flagContainer.removeChild(e.target); flagContainer.removeChild(e.target);
nsetup.toggleFlagButton('on'); toggleFlagButton('on');
nsetup.save(); saveFlags();
}); });
if (!flag) { // When we add a flag to our selection, save it for when we reload the page. if (!flag) { // When we add a flag to our selection, save it for when we reload the page.
nsetup.save(); saveFlags();
}
} }
},
init: function () { /** Create flag button and initialise our selected flags */
function init() {
let flagsForm = createAndAssign('div', { let flagsForm = createAndAssign('div', {
className: 'flagsForm', className: 'flagsForm',
innerHTML: nsetup.form innerHTML: '<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><div id="flagSelect" ><ul class="hide"></ul><input type="button" value="(You)" onclick=""></div>'
}); });
// Where do we append the flagsForm to? // Where do we append the flagsForm to?
@ -206,19 +158,56 @@ const software = {
if (software.nodegucaDoushio) { document.querySelector('section').append(flagsForm); } // As posts are added the flagForm moves up the page. Could we append this after .section? if (software.nodegucaDoushio) { document.querySelector('section').append(flagsForm); } // As posts are added the flagForm moves up the page. Could we append this after .section?
for (var i in regions) { for (var i in regions) {
nsetup.setFlag(regions[i]); setFlag(regions[i]);
} }
document.getElementById('append_flag_button').addEventListener('click', document.getElementById('append_flag_button').addEventListener('click',
() => nsetup.flagsLoaded ? nsetup.setFlag() : alert('Load flags before adding them.')); () => flagsLoaded ? setFlag() : alert('Load flags before adding them.'));
document.getElementById('flagLoad').addEventListener('click', nsetup.fillHtml, { once: true }); document.getElementById('flagLoad').addEventListener('click', makeFlagSelect, { once: true });
}, }
toggleFlagButton: state => document.getElementById('append_flag_button').disabled = state === 'off' ? true : false
}; /** Get flag data from server and fill flags form. */
function makeFlagSelect() {
makeRequest(
"GET",
back_end + api_flags,
"version=" + encodeURIComponent(version),
function (resp) {
debug('Loading flags.');
if (resp.status !== 200) {
return;
}
/** Select all of the post numbers on the page. let flagSelect = document.getElementById('flagSelect');
* @param {string} selector - The CSS selector of the post numbers. */ let flagList = flagSelect.querySelector('ul');
let flagInput = flagSelect.querySelector('input');
let flags = resp.responseText.split('\n');
for (var i = 0; i < flags.length; i++) {
let flag = flags[i];
flagList.appendChild(createAndAssign('li', {
innerHTML: '<img src="' + back_end + flag_dir + flag + '.png" title="' + flag + '"> <span>' + flag + '</span>'
}));
}
flagSelect.addEventListener('click', (e) => {
listItem = e.target.nodeName === 'LI' ? e.target : e.target.parentNode; // So we can click the flag image and still select the flag.
if (listItem.nodeName === 'LI') {
flagInput.value = listItem.querySelector('span').innerHTML;
}
flagList.classList.toggle('hide');
});
document.getElementById('flagLoad').style.display = 'none';
document.querySelector('.flagsForm').style.marginRight = "200px"; // Element has position: absolute and is ~200px long.
flagSelect.style.display = 'inline-block';
flagsLoaded = true;
});
}
/** add all of thhe post numbers on the page to postNrs.
* @param {string} selector - The CSS selector who's id is the post number. */
function getPosts(selector) { function getPosts(selector) {
let posts = document.querySelectorAll(selector); let posts = document.querySelectorAll(selector);
@ -233,7 +222,7 @@ const software = {
/** Take the response from resolveRefFlags and append flags to their respective post numbers. /** Take the response from resolveRefFlags and append flags to their respective post numbers.
* @param {XMLHttpRequest} response - The response data from resolveRefFlags. */ * @param {XMLHttpRequest} response - The response data from resolveRefFlags. */
function onFlagsLoad(response) { function loadFlags(response) {
debug('JSON: ' + response.responseText); debug('JSON: ' + response.responseText);
var jsonData = JSON.parse(response.responseText); var jsonData = JSON.parse(response.responseText);
@ -277,47 +266,49 @@ const software = {
} }
/** Get flags from the database using values in postNrs and pass the response on to onFlagsLoad */ /** Get flags from the database using values in postNrs and pass the response on to onFlagsLoad */
function resolveRefFlags() { function resolveFlags() {
debug('Board is: ' + board_id); debug('Board is: ' + board_id);
MakeRequest( makeRequest(
'POST', 'POST',
back_end + api_get, back_end + api_get,
'post_nrs=' + encodeURIComponent(postNrs) + '&board=' + encodeURIComponent(board_id) + '&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); console.log('[bantflags] Couldn\'t load flags. Refresh the page.');
return; return;
} }
onFlagsLoad(resp); loadFlags(resp);
} }
); );
} }
// This one await is what stops regular bantflags from working with GM4 lole. (async () => { // This one await is why we need a seperate script ;_;
regions = await GM.getValue(nsetup.namespace); regions = await GM.getValue(namespace);
if (!regions) { // No flags set. if (!regions) { // Should only be called before you set flags for the first time.
regions = []; regions = [];
window.confirm('[BantFlags]: No Flags detected.'); window.confirm('[BantFlags]: No Flags detected.\nIf this is your first time running bantflags, look for the "Click to load flags." button at the bottom right of the thread, then select your flag and press the ">>" button.');
} }
// See Docs/styles.css // See Docs/styles.css
addGlobalStyle('.flagsForm{float: right; clear: right; margin: 20px 10px;} #flagSelect{display:none;} .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;}} #flagSelect ul {list-style-type: none;padding: 0;margin-bottom: 0;cursor: pointer;bottom: 100%;height: 200px;overflow: auto;position: absolute;width:200px;background-color:#fff}#flagSelect ul li {display: block;}#flagSelect ul li:hover {background-color: #ddd;}#flagSelect {position: absolute;}#flagSelect input {width: 200px;} #flagSelect .hide {display: none;}#flagSelect img {margin-left: 2px;}'); addGlobalStyle('.flagsForm{float: right; clear: right; margin: 20px 10px;} #flagSelect{display:none;} .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;}} #flagSelect ul {list-style-type: none;padding: 0;margin-bottom: 0;cursor: pointer;bottom: 100%;height: 200px;overflow: auto;position: absolute;width:200px;background-color:#fff}#flagSelect ul li {display: block;}#flagSelect ul li:hover {background-color: #ddd;}#flagSelect {position: absolute;}#flagSelect input {width: 200px;} #flagSelect .hide {display: none;}#flagSelect img {margin-left: 2px;}');
// Flags need to be parsed differently and have different styles depending on board software. // We get flags using different selectors, and we need to align them differently.
if (software.yotsuba) { if (software.yotsuba) {
debug('4chan'); debug('4chan');
board_id = 'bant'; board_id = 'bant';
getPosts('.postContainer'); getPosts('.postContainer');
addGlobalStyle('.bantFlag {padding: 0px 0px 0px 5px; vertical-align:;display: inline-block; width: 16px; height: 11px; position: relative;} .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;} .flag{top: 0px !important;left: -1px !important}');
init();
} }
if (software.nodegucaDoushio) { if (software.nodegucaDoushio) {
debug('Nineball'); debug('Nineball');
board_id = window.location.pathname.split('/')[1]; // 'nap' or 'srsbsn' board_id = window.location.pathname.split('/')[1];
getPosts('section[id], article[id]'); getPosts('section[id], article[id]');
addGlobalStyle('.bantFlag {cursor: default} .bantFlag img {pointer-events: none;}'); addGlobalStyle('.bantFlag {cursor: default} .bantFlag img {pointer-events: none;}');
init();
} }
if (software.foolfuuka) { if (software.foolfuuka) {
@ -328,12 +319,13 @@ const software = {
addGlobalStyle('.bantFlag{top: -2px !important;left: -1px !important}'); addGlobalStyle('.bantFlag{top: -2px !important;left: -1px !important}');
} }
resolveRefFlags(); resolveFlags();
})();
if (software.yotsuba) { if (software.yotsuba) {
const GetEvDetail = e => e.detail || e.wrappedJSObject.detail; const GetEvDetail = e => e.detail || e.wrappedJSObject.detail;
const postFlags = post_nr => MakeRequest( const postFlags = post_nr => makeRequest(
'POST', 'POST',
back_end + api_post, back_end + api_post,
'post_nr=' + encodeURIComponent(post_nr) + '&board=' + encodeURIComponent(board_id) + '&regions=' + encodeURIComponent(regions) + '&version=' + encodeURIComponent(version), 'post_nr=' + encodeURIComponent(post_nr) + '&board=' + encodeURIComponent(board_id) + '&regions=' + encodeURIComponent(regions) + '&version=' + encodeURIComponent(version),
@ -358,7 +350,7 @@ const software = {
postNrs.push(post_nr); postNrs.push(post_nr);
}); });
resolveRefFlags(); resolveFlags();
}, false); }, false);
document.addEventListener('4chanThreadUpdated', function (e) { document.addEventListener('4chanThreadUpdated', function (e) {
@ -372,15 +364,11 @@ const software = {
postNrs.push(post_nr); postNrs.push(post_nr);
}); });
resolveRefFlags(); resolveFlags();
}, false); }, false);
nsetup.init();
} }
if (software.nodegucaDoushio) { if (software.nodegucaDoushio) {
nsetup.init();
// This is poking at the mutations made on the page to figure out what happened and thus what actions to take. // This is poking at the mutations made on the page to figure out what happened and thus what actions to take.
// There is full support for nodeguca but I don't have a Doushio board I feel comfortable spamming to ensure it works properly there. There is at least partial support. // There is full support for nodeguca but I don't have a Doushio board I feel comfortable spamming to ensure it works properly there. There is at least partial support.
new MutationObserver(function (mutations) { new MutationObserver(function (mutations) {
@ -392,30 +380,29 @@ const software = {
if (mutation.target.nodeName === 'THREADS' && firstAddedNode !== 'HR' && firstAddedNode !== 'SECTION') { if (mutation.target.nodeName === 'THREADS' && firstAddedNode !== 'HR' && firstAddedNode !== 'SECTION') {
board_id = window.location.pathname.split('/')[1]; board_id = window.location.pathname.split('/')[1];
setTimeout(getPosts('section[id], article[id]'), 2000); setTimeout(getPosts('section[id], article[id]'), 2000);
resolveRefFlags(); resolveFlags();
nsetup.init(); init();
} }
// You post. // You post.
if (firstAddedNode === 'HEADER') { if (firstAddedNode === 'HEADER') {
let data = 'post_nr=' + encodeURIComponent(mutation.target.id) + '&board=' + encodeURIComponent(board_id) + '&regions=' + encodeURIComponent(regions) + '&version=' + encodeURIComponent(version); let data = 'post_nr=' + encodeURIComponent(mutation.target.id) + '&board=' + encodeURIComponent(board_id) + '&regions=' + encodeURIComponent(regions) + '&version=' + encodeURIComponent(version);
MakeRequest( makeRequest(
'POST', 'POST',
back_end + api_post, back_end + api_post,
data, data,
function () { function () {
postNrs.push(mutation.target.id); postNrs.push(mutation.target.id);
resolveRefFlags(); resolveFlags();
}); });
} }
// Someone else posts. Checks to see if you're hovering over a post. // Someone else posts. Checks to see if you're hovering over a post.
if (firstAddedNode === 'ARTICLE' && mutation.target.nodeName !== "BODY" && mutation.target.id !== 'hover_overlay') { if (firstAddedNode === 'ARTICLE' && mutation.target.nodeName !== "BODY" && mutation.target.id !== 'hover_overlay') {
postNrs.push(mutation.addedNodes[0].id); postNrs.push(mutation.addedNodes[0].id);
setTimeout(resolveRefFlags, 1500); setTimeout(resolveFlags, 1500);
} }
} }
}); });
}).observe(document.body, { childList: true, subtree: true }); }).observe(document.body, { childList: true, subtree: true });
} }
})();

@ -11,7 +11,7 @@
// @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/
// @exclude http*://thebarchive.com/bant/statistics/ // @exclude http*://thebarchive.com/bant/statistics/
// @version 1.4.0 // @version 1.4.1
// @grant GM_xmlhttpRequest // @grant GM_xmlhttpRequest
// @grant GM_getValue // @grant GM_getValue
// @grant GM_setValue // @grant GM_setValue

@ -11,7 +11,7 @@
// @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/
// @exclude http*://thebarchive.com/bant/statistics/ // @exclude http*://thebarchive.com/bant/statistics/
// @version 1.4.0 // @version 1.4.1
// @grant GM_xmlhttpRequest // @grant GM_xmlhttpRequest
// @grant GM_getValue // @grant GM_getValue
// @grant GM_setValue // @grant GM_setValue
@ -40,21 +40,21 @@ if (typeof GM_setValue === 'undefined') {
// //
// DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR FLAGS SHOULD BE CONFIGURED USING THE FLAG SELECT // DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR FLAGS SHOULD BE CONFIGURED USING THE FLAG SELECT
// //
const postRemoveCounter = 60;
const requestRetryInterval = 5000; // TODO: maybe a max retries counter as well?
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';
const flag_dir = 'flags/'; const flag_dir = 'flags/';
const api_get = 'api/get'; const api_get = 'api/get';
const api_post = 'api/post'; const api_post = 'api/post';
const namespace = 'BintFlegs';
// If you increase this the server will ignore your post. // If you increase this the server will ignore your post.
const max_flags = 30; const max_flags = 30;
var regions = []; // The flags we have selected. let regions = []; // The flags we have selected.
var postNrs = []; // all post numbers in the thread. let postNrs = []; // all post numbers in the thread.
var board_id = ""; // The board we get flags for. let board_id = ""; // The board we get flags for.
let flagsLoaded = false;
// //
// DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR FLAGS SHOULD BE CONFIGURED USING THE FLAG SELECT // DO NOT EDIT ANYTHING IN THIS SCRIPT DIRECTLY - YOUR FLAGS SHOULD BE CONFIGURED USING THE FLAG SELECT
// //
@ -72,6 +72,8 @@ const software = {
* @returns {object} The HTML tag created */ * @returns {object} The HTML tag created */
const createAndAssign = (element, source) => Object.assign(document.createElement(element), source); const createAndAssign = (element, source) => Object.assign(document.createElement(element), source);
const toggleFlagButton = state => document.getElementById('append_flag_button').disabled = state === 'off' ? true : false;
/** Add a stylesheet to the head of the document. /** Add a stylesheet to the head of the document.
* @param {string} css - The CSS rules for the stylesheet. * @param {string} css - The CSS rules for the stylesheet.
* @returns {object} The style element appended to the head */ * @returns {object} The style element appended to the head */
@ -93,7 +95,7 @@ function debug(text) {
* @param {string} url - The URL of the request. * @param {string} url - The URL of the request.
* @param {string} data - text for the form body. * @param {string} data - text for the form body.
* @param {Function} func - The function run when we recieve a response. Response data is sent directly to it. */ * @param {Function} func - The function run when we recieve a response. Response data is sent directly to it. */
const MakeRequest = ((method, url, data, func) => { const makeRequest = ((method, url, data, func) => {
GM_xmlhttpRequest({ GM_xmlhttpRequest({
method: method, method: method,
url: url, url: url,
@ -103,70 +105,21 @@ const MakeRequest = ((method, url, data, func) => {
}); });
}); });
/** Try some MakeRequest again if it fails. /** Itterate over selected flags are store them across browser sessions.*/
* @param {function} func - The function to retry. function saveFlags() {
* @param {XMLHttpRequest} resp - The XMLHttpResponse from the failed request.*/ regions = [];
function retry(func, resp) {
console.log('[BantFlags] Could not fetch flags, status: ' + resp.status);
console.log(resp.statusText);
setTimeout(func, requestRetryInterval);
}
// TODO: this shouldn't be a object.
var nsetup = { // not anymore a clone of the original setup
namespace: 'BintFlegs', // TODO: should be const.
flagsLoaded: false,
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><div id="flagSelect" ><ul class="hide"></ul><input type="button" value="(You)" onclick=""></div>',
fillHtml: function () { // TODO: this function should have a better name. Only called by nsetup.init, can be inlined?
MakeRequest(
"GET",
back_end + api_flags,
"version=" + encodeURIComponent(version),
function (resp) {
debug('Loading flags.');
if (resp.status !== 200) {
retry(nsetup.fillHtml, resp);
return;
}
let flagSelect = document.getElementById('flagSelect');
let flagList = flagSelect.querySelector('ul');
let flagInput = flagSelect.querySelector('input');
let flags = resp.responseText.split('\n');
for (var i = 0; i < flags.length; i++) {
let flag = flags[i];
flagList.appendChild(createAndAssign('li', {
innerHTML: '<img src="' + back_end + flag_dir + flag + '.png" title="' + flag + '"> <span>' + flag + '</span>'
}));
}
flagSelect.addEventListener('click', (e) => {
listItem = e.target.nodeName === 'LI' ? e.target : e.target.parentNode;
if (listItem.nodeName === 'LI') {
flagInput.value = listItem.querySelector('span').innerHTML;
}
flagList.classList.toggle('hide');
});
document.getElementById('flagLoad').style.display = 'none';
document.querySelector('.flagsForm').style.marginRight = "200px"; // Element has position: absolute and is ~200px long.
flagSelect.style.display = 'inline-block';
nsetup.flagsLoaded = true;
});
},
save: function () {
let storedFlags = [];
let selectedFlags = document.getElementsByClassName("bantflags_flag"); let selectedFlags = document.getElementsByClassName("bantflags_flag");
for (var i = 0; i < selectedFlags.length; i++) { for (var i = 0; i < selectedFlags.length; i++) {
storedFlags[i] = selectedFlags[i].title; regions[i] = selectedFlags[i].title;
} }
GM_setValue(nsetup.namespace, storedFlags); GM_setValue(namespace, regions);
regions = GM_getValue(nsetup.namespace); // TODO: this could be selectedFlags? GM_getValue is expensive. }
},
setFlag: function (flag) { /** Add a flag to our selection.
* @param {string} flag - The flag to add to our selection. If no value is passed it takes the current value from the flagSelect. */
function setFlag(flag) {
let UID = Math.random().toString(36).substring(7); let UID = Math.random().toString(36).substring(7);
let flagName = flag ? flag : document.querySelector('#flagSelect input').value; let flagName = flag ? flag : document.querySelector('#flagSelect input').value;
let flagContainer = document.getElementById('bantflags_container'); let flagContainer = document.getElementById('bantflags_container');
@ -179,23 +132,25 @@ var nsetup = { // not anymore a clone of the original setup
})); }));
if (flagContainer.children.length >= max_flags) { if (flagContainer.children.length >= max_flags) {
nsetup.toggleFlagButton('off'); toggleFlagButton('off');
} }
document.getElementById(UID).addEventListener("click", (e) => { document.getElementById(UID).addEventListener("click", (e) => {
flagContainer.removeChild(e.target); flagContainer.removeChild(e.target);
nsetup.toggleFlagButton('on'); toggleFlagButton('on');
nsetup.save(); saveFlags();
}); });
if (!flag) { // When we add a flag to our selection, save it for when we reload the page. if (!flag) { // When we add a flag to our selection, save it for when we reload the page.
nsetup.save(); saveFlags();
}
} }
},
init: function () { /** Create flag button and initialise our selected flags */
function init() {
let flagsForm = createAndAssign('div', { let flagsForm = createAndAssign('div', {
className: 'flagsForm', className: 'flagsForm',
innerHTML: nsetup.form innerHTML: '<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><div id="flagSelect" ><ul class="hide"></ul><input type="button" value="(You)" onclick=""></div>'
}); });
// Where do we append the flagsForm to? // Where do we append the flagsForm to?
@ -203,16 +158,53 @@ var nsetup = { // not anymore a clone of the original setup
if (software.nodegucaDoushio) { document.querySelector('section').append(flagsForm); } // As posts are added the flagForm moves up the page. Could we append this after .section? if (software.nodegucaDoushio) { document.querySelector('section').append(flagsForm); } // As posts are added the flagForm moves up the page. Could we append this after .section?
for (var i in regions) { for (var i in regions) {
nsetup.setFlag(regions[i]); setFlag(regions[i]);
} }
document.getElementById('append_flag_button').addEventListener('click', document.getElementById('append_flag_button').addEventListener('click',
() => nsetup.flagsLoaded ? nsetup.setFlag() : alert('Load flags before adding them.')); () => flagsLoaded ? setFlag() : alert('Load flags before adding them.'));
document.getElementById('flagLoad').addEventListener('click', nsetup.fillHtml, { once: true }); document.getElementById('flagLoad').addEventListener('click', makeFlagSelect, { once: true });
}, }
toggleFlagButton: state => document.getElementById('append_flag_button').disabled = state === 'off' ? true : false
}; /** Get flag data from server and fill flags form. */
function makeFlagSelect() {
makeRequest(
"GET",
back_end + api_flags,
"version=" + encodeURIComponent(version),
function (resp) {
debug('Loading flags.');
if (resp.status !== 200) {
return;
}
let flagSelect = document.getElementById('flagSelect');
let flagList = flagSelect.querySelector('ul');
let flagInput = flagSelect.querySelector('input');
let flags = resp.responseText.split('\n');
for (var i = 0; i < flags.length; i++) {
let flag = flags[i];
flagList.appendChild(createAndAssign('li', {
innerHTML: '<img src="' + back_end + flag_dir + flag + '.png" title="' + flag + '"> <span>' + flag + '</span>'
}));
}
flagSelect.addEventListener('click', (e) => {
listItem = e.target.nodeName === 'LI' ? e.target : e.target.parentNode; // So we can click the flag image and still select the flag.
if (listItem.nodeName === 'LI') {
flagInput.value = listItem.querySelector('span').innerHTML;
}
flagList.classList.toggle('hide');
});
document.getElementById('flagLoad').style.display = 'none';
document.querySelector('.flagsForm').style.marginRight = "200px"; // Element has position: absolute and is ~200px long.
flagSelect.style.display = 'inline-block';
flagsLoaded = true;
});
}
/** add all of thhe post numbers on the page to postNrs. /** add all of thhe post numbers on the page to postNrs.
* @param {string} selector - The CSS selector who's id is the post number. */ * @param {string} selector - The CSS selector who's id is the post number. */
@ -230,7 +222,7 @@ function getPosts(selector) {
/** Take the response from resolveRefFlags and append flags to their respective post numbers. /** Take the response from resolveRefFlags and append flags to their respective post numbers.
* @param {XMLHttpRequest} response - The response data from resolveRefFlags. */ * @param {XMLHttpRequest} response - The response data from resolveRefFlags. */
function onFlagsLoad(response) { function loadFlags(response) {
debug('JSON: ' + response.responseText); debug('JSON: ' + response.responseText);
var jsonData = JSON.parse(response.responseText); var jsonData = JSON.parse(response.responseText);
@ -274,27 +266,26 @@ function onFlagsLoad(response) {
} }
/** Get flags from the database using values in postNrs and pass the response on to onFlagsLoad */ /** Get flags from the database using values in postNrs and pass the response on to onFlagsLoad */
function resolveRefFlags() { function resolveFlags() {
debug('Board is: ' + board_id); debug('Board is: ' + board_id);
MakeRequest( makeRequest(
'POST', 'POST',
back_end + api_get, back_end + api_get,
'post_nrs=' + encodeURIComponent(postNrs) + '&board=' + encodeURIComponent(board_id) + '&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); console.log('[bantflags] Couldn\'t load flags. Refresh the page.');
return; return;
} }
onFlagsLoad(resp); loadFlags(resp);
} }
); );
} }
// This should only happen before you set flags for the first time. regions = GM_getValue(namespace);
regions = GM_getValue(nsetup.namespace); if (!regions) { // Should only be called before you set flags for the first time.
if (!regions) {
regions = []; regions = [];
window.confirm('[BantFlags]: No Flags detected.'); window.confirm('[BantFlags]: No Flags detected.\nIf this is your first time running bantflags, look for the "Click to load flags." button at the bottom right of the thread, then select your flag and press the ">>" button.');
} }
// See Docs/styles.css // See Docs/styles.css
@ -307,14 +298,16 @@ if (software.yotsuba) {
getPosts('.postContainer'); getPosts('.postContainer');
addGlobalStyle('.bantFlag {padding: 0px 0px 0px 5px; vertical-align:;display: inline-block; width: 16px; height: 11px; position: relative;} .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;} .flag{top: 0px !important;left: -1px !important}');
init();
} }
if (software.nodegucaDoushio) { if (software.nodegucaDoushio) {
debug('Nineball'); debug('Nineball');
board_id = window.location.pathname.split('/')[1]; // 'nap' or 'srsbsn' board_id = window.location.pathname.split('/')[1];
getPosts('section[id], article[id]'); getPosts('section[id], article[id]');
addGlobalStyle('.bantFlag {cursor: default} .bantFlag img {pointer-events: none;}'); addGlobalStyle('.bantFlag {cursor: default} .bantFlag img {pointer-events: none;}');
init();
} }
if (software.foolfuuka) { if (software.foolfuuka) {
@ -325,12 +318,12 @@ if (software.foolfuuka) {
addGlobalStyle('.bantFlag{top: -2px !important;left: -1px !important}'); addGlobalStyle('.bantFlag{top: -2px !important;left: -1px !important}');
} }
resolveRefFlags(); resolveFlags();
if (software.yotsuba) { if (software.yotsuba) {
const GetEvDetail = e => e.detail || e.wrappedJSObject.detail; const GetEvDetail = e => e.detail || e.wrappedJSObject.detail;
const postFlags = post_nr => MakeRequest( const postFlags = post_nr => makeRequest(
'POST', 'POST',
back_end + api_post, back_end + api_post,
'post_nr=' + encodeURIComponent(post_nr) + '&board=' + encodeURIComponent(board_id) + '&regions=' + encodeURIComponent(regions) + '&version=' + encodeURIComponent(version), 'post_nr=' + encodeURIComponent(post_nr) + '&board=' + encodeURIComponent(board_id) + '&regions=' + encodeURIComponent(regions) + '&version=' + encodeURIComponent(version),
@ -355,7 +348,7 @@ if (software.yotsuba) {
postNrs.push(post_nr); postNrs.push(post_nr);
}); });
resolveRefFlags(); resolveFlags();
}, false); }, false);
document.addEventListener('4chanThreadUpdated', function (e) { document.addEventListener('4chanThreadUpdated', function (e) {
@ -369,15 +362,11 @@ if (software.yotsuba) {
postNrs.push(post_nr); postNrs.push(post_nr);
}); });
resolveRefFlags(); resolveFlags();
}, false); }, false);
nsetup.init();
} }
if (software.nodegucaDoushio) { if (software.nodegucaDoushio) {
nsetup.init();
// This is poking at the mutations made on the page to figure out what happened and thus what actions to take. // This is poking at the mutations made on the page to figure out what happened and thus what actions to take.
// There is full support for nodeguca but I don't have a Doushio board I feel comfortable spamming to ensure it works properly there. There is at least partial support. // There is full support for nodeguca but I don't have a Doushio board I feel comfortable spamming to ensure it works properly there. There is at least partial support.
new MutationObserver(function (mutations) { new MutationObserver(function (mutations) {
@ -389,27 +378,27 @@ if (software.nodegucaDoushio) {
if (mutation.target.nodeName === 'THREADS' && firstAddedNode !== 'HR' && firstAddedNode !== 'SECTION') { if (mutation.target.nodeName === 'THREADS' && firstAddedNode !== 'HR' && firstAddedNode !== 'SECTION') {
board_id = window.location.pathname.split('/')[1]; board_id = window.location.pathname.split('/')[1];
setTimeout(getPosts('section[id], article[id]'), 2000); setTimeout(getPosts('section[id], article[id]'), 2000);
resolveRefFlags(); resolveFlags();
nsetup.init(); init();
} }
// You post. // You post.
if (firstAddedNode === 'HEADER') { if (firstAddedNode === 'HEADER') {
let data = 'post_nr=' + encodeURIComponent(mutation.target.id) + '&board=' + encodeURIComponent(board_id) + '&regions=' + encodeURIComponent(regions) + '&version=' + encodeURIComponent(version); let data = 'post_nr=' + encodeURIComponent(mutation.target.id) + '&board=' + encodeURIComponent(board_id) + '&regions=' + encodeURIComponent(regions) + '&version=' + encodeURIComponent(version);
MakeRequest( makeRequest(
'POST', 'POST',
back_end + api_post, back_end + api_post,
data, data,
function () { function () {
postNrs.push(mutation.target.id); postNrs.push(mutation.target.id);
resolveRefFlags(); resolveFlags();
}); });
} }
// Someone else posts. Checks to see if you're hovering over a post. // Someone else posts. Checks to see if you're hovering over a post.
if (firstAddedNode === 'ARTICLE' && mutation.target.nodeName !== "BODY" && mutation.target.id !== 'hover_overlay') { if (firstAddedNode === 'ARTICLE' && mutation.target.nodeName !== "BODY" && mutation.target.id !== 'hover_overlay') {
postNrs.push(mutation.addedNodes[0].id); postNrs.push(mutation.addedNodes[0].id);
setTimeout(resolveRefFlags, 1500); setTimeout(resolveFlags, 1500);
} }
} }
}); });

Loading…
Cancel
Save