add canvas cleaner
This commit is contained in:
parent
9122f3e0a0
commit
8d0866c0de
|
@ -10,11 +10,13 @@ import { t } from 'ttag';
|
||||||
import { getToday, dateToString } from '../core/utils';
|
import { getToday, dateToString } from '../core/utils';
|
||||||
|
|
||||||
const keptState = {
|
const keptState = {
|
||||||
coords: null,
|
coords: '',
|
||||||
tlcoords: null,
|
tlcoords: '',
|
||||||
brcoords: null,
|
brcoords: '',
|
||||||
tlrcoords: null,
|
tlrcoords: '',
|
||||||
brrcoords: null,
|
brrcoords: '',
|
||||||
|
tlccoords: '',
|
||||||
|
brccoords: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
async function submitImageAction(
|
async function submitImageAction(
|
||||||
|
@ -80,6 +82,26 @@ async function submitRollback(
|
||||||
callback(await resp.text());
|
callback(await resp.text());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function submitCanvasCleaner(
|
||||||
|
action,
|
||||||
|
canvas,
|
||||||
|
tlcoords,
|
||||||
|
brcoords,
|
||||||
|
callback,
|
||||||
|
) {
|
||||||
|
const data = new FormData();
|
||||||
|
data.append('cleaneraction', action);
|
||||||
|
data.append('canvasid', canvas);
|
||||||
|
data.append('ulcoor', tlcoords);
|
||||||
|
data.append('brcoor', brcoords);
|
||||||
|
const resp = await fetch('./api/modtools', {
|
||||||
|
credentials: 'include',
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
callback(await resp.text());
|
||||||
|
}
|
||||||
|
|
||||||
async function submitIPAction(
|
async function submitIPAction(
|
||||||
action,
|
action,
|
||||||
callback,
|
callback,
|
||||||
|
@ -113,6 +135,41 @@ async function getModList(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getCleanerStats(
|
||||||
|
callback,
|
||||||
|
) {
|
||||||
|
const data = new FormData();
|
||||||
|
data.append('cleanerstat', true);
|
||||||
|
const resp = await fetch('./api/modtools', {
|
||||||
|
credentials: 'include',
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
if (resp.ok) {
|
||||||
|
callback(await resp.json());
|
||||||
|
} else {
|
||||||
|
callback({
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCleanerCancel(
|
||||||
|
callback,
|
||||||
|
) {
|
||||||
|
const data = new FormData();
|
||||||
|
data.append('cleanercancel', true);
|
||||||
|
const resp = await fetch('./api/modtools', {
|
||||||
|
credentials: 'include',
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
if (resp.ok) {
|
||||||
|
callback(await resp.text());
|
||||||
|
} else {
|
||||||
|
callback('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function submitRemMod(
|
async function submitRemMod(
|
||||||
userId,
|
userId,
|
||||||
callback,
|
callback,
|
||||||
|
@ -151,6 +208,7 @@ function Modtools() {
|
||||||
|
|
||||||
const [selectedCanvas, selectCanvas] = useState(0);
|
const [selectedCanvas, selectCanvas] = useState(0);
|
||||||
const [imageAction, selectImageAction] = useState('build');
|
const [imageAction, selectImageAction] = useState('build');
|
||||||
|
const [cleanAction, selectCleanAction] = useState('spare');
|
||||||
const [iPAction, selectIPAction] = useState('ban');
|
const [iPAction, selectIPAction] = useState('ban');
|
||||||
const [protAction, selectProtAction] = useState('protect');
|
const [protAction, selectProtAction] = useState('protect');
|
||||||
const [date, selectDate] = useState(maxDate);
|
const [date, selectDate] = useState(maxDate);
|
||||||
|
@ -159,9 +217,12 @@ function Modtools() {
|
||||||
const [brcoords, selectBRCoords] = useState(keptState.brcoords);
|
const [brcoords, selectBRCoords] = useState(keptState.brcoords);
|
||||||
const [tlrcoords, selectTLRCoords] = useState(keptState.tlrcoords);
|
const [tlrcoords, selectTLRCoords] = useState(keptState.tlrcoords);
|
||||||
const [brrcoords, selectBRRCoords] = useState(keptState.brrcoords);
|
const [brrcoords, selectBRRCoords] = useState(keptState.brrcoords);
|
||||||
const [modName, selectModName] = useState(null);
|
const [tlccoords, selectTLCCoords] = useState(keptState.tlrcoords);
|
||||||
|
const [brccoords, selectBRCCoords] = useState(keptState.brrcoords);
|
||||||
|
const [modName, selectModName] = useState('');
|
||||||
const [resp, setResp] = useState(null);
|
const [resp, setResp] = useState(null);
|
||||||
const [modlist, setModList] = useState([]);
|
const [modlist, setModList] = useState([]);
|
||||||
|
const [cleanerstats, setCleanerStats] = useState({});
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
|
@ -193,12 +254,34 @@ function Modtools() {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let descCleanAction;
|
||||||
|
switch (cleanAction) {
|
||||||
|
case 'spare':
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
descCleanAction = t`Clean spare pixels that are surrounded by unset pixels`;
|
||||||
|
break;
|
||||||
|
case 'spareext':
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
descCleanAction = t`Clean spare pixels that are surrounded by a single other color`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userlvl === 1) {
|
if (userlvl === 1) {
|
||||||
getModList((mods) => setModList(mods));
|
getModList((mods) => setModList(mods));
|
||||||
}
|
}
|
||||||
|
if (userlvl > 0) {
|
||||||
|
getCleanerStats((stats) => setCleanerStats(stats));
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const cleanerStatusString = (!cleanerstats.running)
|
||||||
|
? t`Status: Not running`
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
: `Status: ${cleanerstats.method} from ${cleanerstats.tl} to ${cleanerstats.br} on canvas ${canvases[cleanerstats.canvasId].ident} to ${cleanerstats.percent} done`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: 'center', paddingLeft: '5%', paddingRight: '5%' }}>
|
<div style={{ textAlign: 'center', paddingLeft: '5%', paddingRight: '5%' }}>
|
||||||
{resp && (
|
{resp && (
|
||||||
|
@ -227,6 +310,7 @@ function Modtools() {
|
||||||
)}
|
)}
|
||||||
<p className="modalcotext">Choose Canvas:
|
<p className="modalcotext">Choose Canvas:
|
||||||
<select
|
<select
|
||||||
|
value={selectedCanvas}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const sel = e.target;
|
const sel = e.target;
|
||||||
selectCanvas(sel.options[sel.selectedIndex].value);
|
selectCanvas(sel.options[sel.selectedIndex].value);
|
||||||
|
@ -237,7 +321,6 @@ function Modtools() {
|
||||||
? null
|
? null
|
||||||
: (
|
: (
|
||||||
<option
|
<option
|
||||||
selected={canvas === selectedCanvas}
|
|
||||||
value={canvas}
|
value={canvas}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
@ -257,6 +340,7 @@ function Modtools() {
|
||||||
<input type="file" name="image" id="imgfile" />
|
<input type="file" name="image" id="imgfile" />
|
||||||
</p>
|
</p>
|
||||||
<select
|
<select
|
||||||
|
value={imageAction}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const sel = e.target;
|
const sel = e.target;
|
||||||
selectImageAction(sel.options[sel.selectedIndex].value);
|
selectImageAction(sel.options[sel.selectedIndex].value);
|
||||||
|
@ -265,7 +349,6 @@ function Modtools() {
|
||||||
{['build', 'protect', 'wipe'].map((opt) => (
|
{['build', 'protect', 'wipe'].map((opt) => (
|
||||||
<option
|
<option
|
||||||
value={opt}
|
value={opt}
|
||||||
selected={imageAction === opt}
|
|
||||||
>
|
>
|
||||||
{opt}
|
{opt}
|
||||||
</option>
|
</option>
|
||||||
|
@ -320,6 +403,7 @@ function Modtools() {
|
||||||
use protect with image upload and alpha layers)`}
|
use protect with image upload and alpha layers)`}
|
||||||
</p>
|
</p>
|
||||||
<select
|
<select
|
||||||
|
value={protAction}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const sel = e.target;
|
const sel = e.target;
|
||||||
selectProtAction(sel.options[sel.selectedIndex].value);
|
selectProtAction(sel.options[sel.selectedIndex].value);
|
||||||
|
@ -328,14 +412,13 @@ function Modtools() {
|
||||||
{['protect', 'unprotect'].map((opt) => (
|
{['protect', 'unprotect'].map((opt) => (
|
||||||
<option
|
<option
|
||||||
value={opt}
|
value={opt}
|
||||||
selected={protAction === opt}
|
|
||||||
>
|
>
|
||||||
{opt}
|
{opt}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<p className="modalcotext">
|
<p className="modalcotext">
|
||||||
Top-left corner (X_Y):
|
{t`Top-left corner`} (X_Y):
|
||||||
<input
|
<input
|
||||||
value={tlcoords}
|
value={tlcoords}
|
||||||
style={{
|
style={{
|
||||||
|
@ -353,7 +436,7 @@ function Modtools() {
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<p className="modalcotext">
|
<p className="modalcotext">
|
||||||
Bottom-right corner (X_Y):
|
{t`Bottom-right corner`} (X_Y):
|
||||||
<input
|
<input
|
||||||
value={brcoords}
|
value={brcoords}
|
||||||
style={{
|
style={{
|
||||||
|
@ -410,7 +493,7 @@ function Modtools() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<p className="modalcotext">
|
<p className="modalcotext">
|
||||||
Top-left corner (X_Y):
|
{t`Top-left corner`} (X_Y):
|
||||||
<input
|
<input
|
||||||
value={tlrcoords}
|
value={tlrcoords}
|
||||||
style={{
|
style={{
|
||||||
|
@ -428,7 +511,7 @@ function Modtools() {
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<p className="modalcotext">
|
<p className="modalcotext">
|
||||||
Bottom-right corner (X_Y):
|
{t`Bottom-right corner`} (X_Y):
|
||||||
<input
|
<input
|
||||||
value={brrcoords}
|
value={brrcoords}
|
||||||
style={{
|
style={{
|
||||||
|
@ -468,6 +551,107 @@ function Modtools() {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<br />
|
||||||
|
<div className="modaldivider" />
|
||||||
|
<h3 className="modaltitle">{t`Canvas Cleaner`}</h3>
|
||||||
|
<p className="modalcotext">
|
||||||
|
{t`Apply a filter to clean trash in large canvas areas.`}
|
||||||
|
</p>
|
||||||
|
<select
|
||||||
|
value={cleanAction}
|
||||||
|
onChange={(e) => {
|
||||||
|
const sel = e.target;
|
||||||
|
selectCleanAction(sel.options[sel.selectedIndex].value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{['spare', 'spareext'].map((opt) => (
|
||||||
|
<option
|
||||||
|
value={opt}
|
||||||
|
>
|
||||||
|
{opt}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<p className="modalcotext">{descCleanAction}</p>
|
||||||
|
<p className="modalcotext" style={{ fontWeight: 'bold' }}>
|
||||||
|
{cleanerStatusString}
|
||||||
|
</p>
|
||||||
|
<p className="modalcotext">
|
||||||
|
{t`Top-left corner`} (X_Y):
|
||||||
|
<input
|
||||||
|
value={tlccoords}
|
||||||
|
style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '15em',
|
||||||
|
}}
|
||||||
|
type="text"
|
||||||
|
placeholder="X_Y"
|
||||||
|
onChange={(evt) => {
|
||||||
|
const co = evt.target.value.trim();
|
||||||
|
selectTLCCoords(co);
|
||||||
|
keptState.tlccoords = co;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p className="modalcotext">
|
||||||
|
{t`Bottom-right corner`} (X_Y):
|
||||||
|
<input
|
||||||
|
value={brccoords}
|
||||||
|
style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '15em',
|
||||||
|
}}
|
||||||
|
type="text"
|
||||||
|
placeholder="X_Y"
|
||||||
|
onChange={(evt) => {
|
||||||
|
const co = evt.target.value.trim();
|
||||||
|
selectBRCCoords(co);
|
||||||
|
keptState.brccoords = co;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
if (submitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubmitting(true);
|
||||||
|
submitCanvasCleaner(
|
||||||
|
cleanAction,
|
||||||
|
selectedCanvas,
|
||||||
|
tlccoords,
|
||||||
|
brccoords,
|
||||||
|
(ret) => {
|
||||||
|
setSubmitting(false);
|
||||||
|
setResp(ret);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(submitting) ? '...' : t`Submit`}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
if (submitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubmitting(true);
|
||||||
|
getCleanerCancel(
|
||||||
|
(ret) => {
|
||||||
|
setCleanerStats({});
|
||||||
|
setSubmitting(false);
|
||||||
|
setResp(ret);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(submitting) ? '...' : t`Stop Cleaner`}
|
||||||
|
</button>
|
||||||
|
|
||||||
{(userlvl === 1) && (
|
{(userlvl === 1) && (
|
||||||
<div>
|
<div>
|
||||||
<br />
|
<br />
|
||||||
|
@ -477,6 +661,7 @@ function Modtools() {
|
||||||
{t`Do stuff with IPs (one IP per line)`}
|
{t`Do stuff with IPs (one IP per line)`}
|
||||||
</p>
|
</p>
|
||||||
<select
|
<select
|
||||||
|
value={iPAction}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const sel = e.target;
|
const sel = e.target;
|
||||||
selectIPAction(sel.options[sel.selectedIndex].value);
|
selectIPAction(sel.options[sel.selectedIndex].value);
|
||||||
|
@ -485,7 +670,6 @@ function Modtools() {
|
||||||
{['ban', 'unban', 'whitelist', 'unwhitelist'].map((opt) => (
|
{['ban', 'unban', 'whitelist', 'unwhitelist'].map((opt) => (
|
||||||
<option
|
<option
|
||||||
value={opt}
|
value={opt}
|
||||||
selected={iPAction === opt}
|
|
||||||
>
|
>
|
||||||
{opt}
|
{opt}
|
||||||
</option>
|
</option>
|
||||||
|
|
|
@ -80,7 +80,7 @@ class PixelPlainterControls {
|
||||||
viewport.addEventListener('mousemove', this.onMouseMove, false);
|
viewport.addEventListener('mousemove', this.onMouseMove, false);
|
||||||
viewport.addEventListener('mouseup', this.onMouseUp, false);
|
viewport.addEventListener('mouseup', this.onMouseUp, false);
|
||||||
// TODO check if we can go passive here
|
// TODO check if we can go passive here
|
||||||
//viewport.addEventListener('wheel', this.onWheel, { passive: true });
|
// viewport.addEventListener('wheel', this.onWheel, { passive: true });
|
||||||
viewport.addEventListener('wheel', this.onWheel, false);
|
viewport.addEventListener('wheel', this.onWheel, false);
|
||||||
viewport.addEventListener('touchstart', this.onTouchStart, false);
|
viewport.addEventListener('touchstart', this.onTouchStart, false);
|
||||||
viewport.addEventListener('touchend', this.onTouchEnd, false);
|
viewport.addEventListener('touchend', this.onTouchEnd, false);
|
||||||
|
|
406
src/core/CanvasCleaner.js
Normal file
406
src/core/CanvasCleaner.js
Normal file
|
@ -0,0 +1,406 @@
|
||||||
|
/*
|
||||||
|
* runs a filter over a larger canvas area over time
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
setData,
|
||||||
|
getData,
|
||||||
|
setStatus,
|
||||||
|
getStatus,
|
||||||
|
} from '../data/models/CanvasCleaner';
|
||||||
|
import RedisCanvas from '../data/models/RedisCanvas';
|
||||||
|
import {
|
||||||
|
getChunkOfPixel,
|
||||||
|
getCornerOfChunk,
|
||||||
|
} from './utils';
|
||||||
|
import { setPixelByOffset } from './setPixel';
|
||||||
|
import {
|
||||||
|
TILE_SIZE,
|
||||||
|
} from './constants';
|
||||||
|
import logger from './logger';
|
||||||
|
// eslint-disable-next-line import/no-unresolved
|
||||||
|
import canvases from './canvases.json';
|
||||||
|
|
||||||
|
const METHODS = {
|
||||||
|
/*
|
||||||
|
* @param xc, yc chunk coordinates of pixel relative to center chunk
|
||||||
|
* of chunk area
|
||||||
|
*/
|
||||||
|
spare: (xc, yc, clrIgnore, canvasCleaner) => {
|
||||||
|
let rplPxl = null;
|
||||||
|
for (let u = -1; u <= 1; u += 1) {
|
||||||
|
for (let v = -1; v <= 1; v += 1) {
|
||||||
|
const pxl = canvasCleaner.getPixelInChunkArea(xc + u, yc + v);
|
||||||
|
if (pxl === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (u === 0 && v === 0) {
|
||||||
|
if (pxl < clrIgnore) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (pxl >= clrIgnore) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (rplPxl === null) {
|
||||||
|
rplPxl = pxl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rplPxl;
|
||||||
|
},
|
||||||
|
|
||||||
|
spareext: (xc, yc, clrIgnore, canvasCleaner) => {
|
||||||
|
let rplPxl = null;
|
||||||
|
let origPxl = null;
|
||||||
|
for (let u = -1; u <= 1; u += 1) {
|
||||||
|
for (let v = -1; v <= 1; v += 1) {
|
||||||
|
const pxl = canvasCleaner.getPixelInChunkArea(xc + u, yc + v);
|
||||||
|
if (pxl === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (u === 0 && v === 0) {
|
||||||
|
if (pxl < clrIgnore || pxl === rplPxl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
origPxl = pxl;
|
||||||
|
} else {
|
||||||
|
if (rplPxl === null) {
|
||||||
|
rplPxl = pxl;
|
||||||
|
}
|
||||||
|
if (pxl >= clrIgnore) {
|
||||||
|
if (rplPxl < clrIgnore) {
|
||||||
|
rplPxl = pxl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (pxl !== rplPxl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rplPxl === origPxl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return rplPxl;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
class CanvasCleaner {
|
||||||
|
// canvas id: integer
|
||||||
|
canvasId;
|
||||||
|
// coords of top left and bottom right corner of area: integer
|
||||||
|
x;
|
||||||
|
y;
|
||||||
|
u;
|
||||||
|
v;
|
||||||
|
// name of filter method, string
|
||||||
|
methodName;
|
||||||
|
// 3x3 canvas area
|
||||||
|
// [
|
||||||
|
// [AA, AB, AC],
|
||||||
|
// [BA, BB, BC],
|
||||||
|
// [CA, CB, CC],
|
||||||
|
// ]
|
||||||
|
chunks;
|
||||||
|
// chunk coordinates of center BB of chunks
|
||||||
|
centerChunk;
|
||||||
|
// iterator over chunks
|
||||||
|
cIter;
|
||||||
|
// info about chunks of total affected area
|
||||||
|
// cx, cy: top right chunk coords
|
||||||
|
// cw, ch: height and width in chunks
|
||||||
|
// amountChunks: cw * ch
|
||||||
|
cx; cy;
|
||||||
|
cw; ch;
|
||||||
|
amountChunks;
|
||||||
|
// current setTimeout index
|
||||||
|
tick;
|
||||||
|
// if running: boolean
|
||||||
|
running;
|
||||||
|
// stats
|
||||||
|
pxlProcessed;
|
||||||
|
pxlCleaned;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.logger = (text) => {
|
||||||
|
logger.warn(`[CanvasCleaner] ${text}`);
|
||||||
|
};
|
||||||
|
this.cleanChunk = this.cleanChunk.bind(this);
|
||||||
|
this.clearValues();
|
||||||
|
this.loadArgs();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearValues() {
|
||||||
|
this.running = false;
|
||||||
|
this.chunks = [
|
||||||
|
[null, null, null],
|
||||||
|
[null, null, null],
|
||||||
|
[null, null, null],
|
||||||
|
];
|
||||||
|
this.centerChunk = [null, null];
|
||||||
|
this.cIter = 0;
|
||||||
|
this.cx = 0;
|
||||||
|
this.cy = 0;
|
||||||
|
this.cw = 0;
|
||||||
|
this.ch = 0;
|
||||||
|
this.amountChunks = 0;
|
||||||
|
this.pxlProcessed = 0;
|
||||||
|
this.pxlCleaned = 0;
|
||||||
|
this.tick = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadArgs() {
|
||||||
|
const [cIter, running] = await getStatus();
|
||||||
|
if (running) {
|
||||||
|
const [canvasId, x, y, u, v, methodName] = await getData();
|
||||||
|
this.set(canvasId, x, y, u, v, methodName, cIter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.running = false;
|
||||||
|
const str = 'Stopped CanvasCleaner';
|
||||||
|
this.logger(str);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
async cleanChunk() {
|
||||||
|
this.tick = null;
|
||||||
|
const {
|
||||||
|
canvasId, cIter, cw, cx, cy,
|
||||||
|
} = this;
|
||||||
|
const method = METHODS[this.methodName];
|
||||||
|
if (cIter >= this.amountChunks || !this.running) {
|
||||||
|
// finished
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
this.logger(`Finished Cleaning on ${this.x},${this.y}, cleaned ${this.pxlCleaned} / ${this.pxlProcessed} pixels`);
|
||||||
|
this.clearValues();
|
||||||
|
this.saveStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const canvas = canvases[canvasId];
|
||||||
|
let i = (cIter % cw);
|
||||||
|
const j = ((cIter - i) / cw) + cy;
|
||||||
|
i += cx;
|
||||||
|
const clrIgnore = canvas.cli || 0;
|
||||||
|
|
||||||
|
await this.loadChunkArea(i, j);
|
||||||
|
if (this.checkIfChunkInArea(i, j)) {
|
||||||
|
const [xCor, yCor] = getCornerOfChunk(canvas.size, i, j);
|
||||||
|
const xLow = (xCor > this.x) ? 0 : (this.x - xCor);
|
||||||
|
const yLow = (yCor > this.y) ? 0 : (this.y - yCor);
|
||||||
|
const xHigh = (xCor + TILE_SIZE <= this.u) ? TILE_SIZE
|
||||||
|
: (this.u - xCor + 1);
|
||||||
|
const yHigh = (yCor + TILE_SIZE <= this.v) ? TILE_SIZE
|
||||||
|
: (this.v - yCor + 1);
|
||||||
|
for (let xc = xLow; xc < xHigh; xc += 1) {
|
||||||
|
for (let yc = yLow; yc < yHigh; yc += 1) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const rplPxl = await method(xc, yc, clrIgnore, this);
|
||||||
|
this.pxlProcessed += 1;
|
||||||
|
if (rplPxl !== null) {
|
||||||
|
this.pxlCleaned += 1;
|
||||||
|
setPixelByOffset(
|
||||||
|
canvasId,
|
||||||
|
rplPxl,
|
||||||
|
i, j,
|
||||||
|
yc * TILE_SIZE + xc,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveStatus();
|
||||||
|
this.cIter += 1;
|
||||||
|
|
||||||
|
this.tick = setTimeout(this.cleanChunk, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(canvasId, x, y, u, v, methodName, cIter = 0) {
|
||||||
|
if (!METHODS[methodName]) {
|
||||||
|
const str = `Method ${methodName} not available`;
|
||||||
|
this.logger(str);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
const canvas = canvases[canvasId];
|
||||||
|
if (!canvas) {
|
||||||
|
const str = `Canvas ${canvasId} invalid`;
|
||||||
|
this.logger(str);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
if (canvas.v) {
|
||||||
|
const str = 'Can not clean 3D canvas';
|
||||||
|
this.logger(str);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
if (x > u || y > v) {
|
||||||
|
const str = 'Invalid area';
|
||||||
|
this.logger(str);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
const canvasSize = canvas.size;
|
||||||
|
const canvasMaxXY = canvasSize / 2;
|
||||||
|
const canvasMinXY = -canvasMaxXY;
|
||||||
|
if (x < canvasMinXY || y < canvasMinXY
|
||||||
|
|| x >= canvasMaxXY || y >= canvasMaxXY
|
||||||
|
|| u < canvasMinXY || v < canvasMinXY
|
||||||
|
|| u >= canvasMaxXY || v >= canvasMaxXY) {
|
||||||
|
const str = 'Coordinates out of bounds';
|
||||||
|
this.logger(str);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.tick) {
|
||||||
|
this.running = false;
|
||||||
|
clearTimeout(this.tick);
|
||||||
|
}
|
||||||
|
this.canvasId = canvasId;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.u = u;
|
||||||
|
this.v = v;
|
||||||
|
this.cIter = cIter;
|
||||||
|
this.methodName = methodName;
|
||||||
|
const [cx, cy] = getChunkOfPixel(canvas.size, this.x, this.y);
|
||||||
|
this.cx = cx;
|
||||||
|
this.cy = cy;
|
||||||
|
const [cu, cv] = getChunkOfPixel(canvas.size, this.u, this.v);
|
||||||
|
this.cw = cu - cx + 1;
|
||||||
|
this.ch = cv - cy + 1;
|
||||||
|
this.amountChunks = this.cw * this.ch;
|
||||||
|
|
||||||
|
this.running = true;
|
||||||
|
this.tick = setTimeout(this.cleanChunk, 500);
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
this.logger(`Start Cleaning on #${canvas.ident},${this.x},${this.y} till #${canvas.ident},${this.u},${this.v} with method ${methodName}`);
|
||||||
|
this.saveData();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get pixel out of 3x3 chunk area
|
||||||
|
* @param x, y coordinates relative to center chunk
|
||||||
|
* @return integer color index or null if chunk is empty
|
||||||
|
*/
|
||||||
|
getPixelInChunkArea(x, y) {
|
||||||
|
const { chunks } = this;
|
||||||
|
let col;
|
||||||
|
let xc = x;
|
||||||
|
if (x >= 0 && x < TILE_SIZE) {
|
||||||
|
col = 1;
|
||||||
|
} else if (x < 0) {
|
||||||
|
col = 0;
|
||||||
|
xc += TILE_SIZE;
|
||||||
|
} else {
|
||||||
|
col = 2;
|
||||||
|
xc -= TILE_SIZE;
|
||||||
|
}
|
||||||
|
let row;
|
||||||
|
let yc = y;
|
||||||
|
if (y >= 0 && y < TILE_SIZE) {
|
||||||
|
row = 1;
|
||||||
|
} else if (y < 0) {
|
||||||
|
row = 0;
|
||||||
|
yc += TILE_SIZE;
|
||||||
|
} else {
|
||||||
|
row = 2;
|
||||||
|
yc -= TILE_SIZE;
|
||||||
|
}
|
||||||
|
const chunk = chunks[row][col];
|
||||||
|
if (!chunk) return null;
|
||||||
|
// get rid of protection
|
||||||
|
return chunk[yc * TILE_SIZE + xc] & 0x3F;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* load 3x3 chunk area
|
||||||
|
* @param i, j chunk coordinates of center chunk
|
||||||
|
*/
|
||||||
|
async loadChunkArea(i, j) {
|
||||||
|
const { chunks, centerChunk, canvasId } = this;
|
||||||
|
const [io, jo] = centerChunk;
|
||||||
|
const newChunks = [
|
||||||
|
[null, null, null],
|
||||||
|
[null, null, null],
|
||||||
|
[null, null, null],
|
||||||
|
];
|
||||||
|
for (let iRel = -1; iRel <= 1; iRel += 1) {
|
||||||
|
for (let jRel = -1; jRel <= 1; jRel += 1) {
|
||||||
|
let chunk = null;
|
||||||
|
const iAbs = iRel + i;
|
||||||
|
const jAbs = jRel + j;
|
||||||
|
if (
|
||||||
|
io && jo
|
||||||
|
&& iAbs >= io - 1
|
||||||
|
&& iAbs <= io + 1
|
||||||
|
&& jAbs >= jo - 1
|
||||||
|
&& jAbs <= jo + 1
|
||||||
|
) {
|
||||||
|
chunk = chunks[jAbs - jo + 1][iAbs - io + 1];
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
chunk = await RedisCanvas.getChunk(canvasId, iAbs, jAbs);
|
||||||
|
if (!chunk || chunk.length !== TILE_SIZE * TILE_SIZE) {
|
||||||
|
chunk = null;
|
||||||
|
if (chunk) {
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
this.logger(`Chunk ch:${canvasId}:${iAbs}:${jAbs} has invalid size ${chunk.length}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newChunks[jRel + 1][iRel + 1] = chunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.chunks = newChunks;
|
||||||
|
this.centerChunk = [i, j];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* check if chunk exists in area and is not empty
|
||||||
|
* @param i, j chunk to check
|
||||||
|
*/
|
||||||
|
checkIfChunkInArea(i, j) {
|
||||||
|
const { chunks, centerChunk } = this;
|
||||||
|
const [io, jo] = centerChunk;
|
||||||
|
if (
|
||||||
|
io && jo
|
||||||
|
&& i >= io - 1
|
||||||
|
&& i <= io + 1
|
||||||
|
&& j >= jo - 1
|
||||||
|
&& j <= jo + 1
|
||||||
|
) {
|
||||||
|
const col = i - io + 1;
|
||||||
|
const row = j - jo + 1;
|
||||||
|
if (chunks[row][col] !== null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
reportStatus() {
|
||||||
|
return {
|
||||||
|
running: this.running,
|
||||||
|
canvasId: this.canvasId,
|
||||||
|
percent: `${this.cIter} / ${this.amountChunks}`,
|
||||||
|
tl: `${this.x}_${this.y}`,
|
||||||
|
br: `${this.u}_${this.v}`,
|
||||||
|
method: this.methodName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
saveData() {
|
||||||
|
setData(this.canvasId, this.x, this.y, this.u, this.v, this.methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveStatus() {
|
||||||
|
setStatus(this.cIter, this.running);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new CanvasCleaner();
|
|
@ -1,7 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* functions for admintools
|
* functions for admintools
|
||||||
*
|
*
|
||||||
* @flow
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
|
@ -10,8 +9,9 @@ import sharp from 'sharp';
|
||||||
import Sequelize from 'sequelize';
|
import Sequelize from 'sequelize';
|
||||||
import redis from '../data/redis';
|
import redis from '../data/redis';
|
||||||
|
|
||||||
import { modtoolsLogger } from './logger';
|
|
||||||
import { getIPv6Subnet } from '../utils/ip';
|
import { getIPv6Subnet } from '../utils/ip';
|
||||||
|
import { validateCoorRange } from '../utils/validation';
|
||||||
|
import CanvasCleaner from './CanvasCleaner';
|
||||||
import { Blacklist, Whitelist, RegUser } from '../data/models';
|
import { Blacklist, Whitelist, RegUser } from '../data/models';
|
||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import canvases from './canvases.json';
|
import canvases from './canvases.json';
|
||||||
|
@ -27,7 +27,7 @@ import rollbackCanvasArea from './rollback';
|
||||||
* @param ip already sanizized ip
|
* @param ip already sanizized ip
|
||||||
* @return true if successful
|
* @return true if successful
|
||||||
*/
|
*/
|
||||||
export async function executeIPAction(action: string, ips: string): string {
|
export async function executeIPAction(action, ips, logger = null) {
|
||||||
const ipArray = ips.split('\n');
|
const ipArray = ips.split('\n');
|
||||||
let out = '';
|
let out = '';
|
||||||
const splitRegExp = /\s+/;
|
const splitRegExp = /\s+/;
|
||||||
|
@ -46,7 +46,7 @@ export async function executeIPAction(action: string, ips: string): string {
|
||||||
const ipKey = getIPv6Subnet(ip);
|
const ipKey = getIPv6Subnet(ip);
|
||||||
const key = `isprox:${ipKey}`;
|
const key = `isprox:${ipKey}`;
|
||||||
|
|
||||||
modtoolsLogger.info(`ADMINTOOLS: ${action} ${ip}`);
|
if (logger) logger(`${action} ${ip}`);
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'ban':
|
case 'ban':
|
||||||
await Blacklist.findOrCreate({
|
await Blacklist.findOrCreate({
|
||||||
|
@ -89,10 +89,11 @@ export async function executeIPAction(action: string, ips: string): string {
|
||||||
* @return [ret, msg] http status code and message
|
* @return [ret, msg] http status code and message
|
||||||
*/
|
*/
|
||||||
export async function executeImageAction(
|
export async function executeImageAction(
|
||||||
action: string,
|
action,
|
||||||
file: Object,
|
file,
|
||||||
coords: string,
|
coords,
|
||||||
canvasid: string,
|
canvasid,
|
||||||
|
logger = null,
|
||||||
) {
|
) {
|
||||||
if (!coords) {
|
if (!coords) {
|
||||||
return [403, 'Coordinates not defined'];
|
return [403, 'Coordinates not defined'];
|
||||||
|
@ -150,7 +151,7 @@ export async function executeImageAction(
|
||||||
);
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
modtoolsLogger.info(`ADMINTOOLS: Loaded image wth ${pxlCount} pixels to ${x}/${y}`);
|
if (logger) logger(`Loaded image wth ${pxlCount} pixels to #${canvas.ident},${x},${y}`);
|
||||||
return [
|
return [
|
||||||
200,
|
200,
|
||||||
`Successfully loaded image wth ${pxlCount} pixels to ${x}/${y}`,
|
`Successfully loaded image wth ${pxlCount} pixels to ${x}/${y}`,
|
||||||
|
@ -160,6 +161,53 @@ export async function executeImageAction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Execute actions for cleaning/filtering canvas
|
||||||
|
* @param action what to do
|
||||||
|
* @param ulcoor coords of upper-left corner in X_Y format
|
||||||
|
* @param brcoor coords of bottom-right corner in X_Y format
|
||||||
|
* @param canvasid numerical canvas id as string
|
||||||
|
* @return [ret, msg] http status code and message
|
||||||
|
*/
|
||||||
|
export async function executeCleanerAction(
|
||||||
|
action,
|
||||||
|
ulcoor,
|
||||||
|
brcoor,
|
||||||
|
canvasid,
|
||||||
|
logger = null,
|
||||||
|
) {
|
||||||
|
if (!canvasid) {
|
||||||
|
return [403, 'canvasid not defined'];
|
||||||
|
}
|
||||||
|
const canvas = canvases[canvasid];
|
||||||
|
let error = null;
|
||||||
|
if (!ulcoor || !brcoor) {
|
||||||
|
error = 'Not all coordinates defined';
|
||||||
|
} else if (!canvas) {
|
||||||
|
error = 'Invalid canvas selected';
|
||||||
|
} else if (!action) {
|
||||||
|
error = 'No cleanaction given';
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
return [403, error];
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseCoords = validateCoorRange(ulcoor, brcoor, canvas.size);
|
||||||
|
if (typeof parseCoords === 'string') {
|
||||||
|
return [403, parseCoords];
|
||||||
|
}
|
||||||
|
const [x, y, u, v] = parseCoords;
|
||||||
|
|
||||||
|
error = CanvasCleaner.set(canvasid, x, y, u, v, action);
|
||||||
|
if (error) {
|
||||||
|
return [403, error];
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
const report = `Set Canvas Cleaner to ${action} canvas ${canvas.ident} from ${ulcoor} to ${brcoor}`;
|
||||||
|
if (logger) logger(report);
|
||||||
|
return [200, report];
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Execute actions for protecting areas
|
* Execute actions for protecting areas
|
||||||
* @param action what to do
|
* @param action what to do
|
||||||
|
@ -169,46 +217,23 @@ export async function executeImageAction(
|
||||||
* @return [ret, msg] http status code and message
|
* @return [ret, msg] http status code and message
|
||||||
*/
|
*/
|
||||||
export async function executeProtAction(
|
export async function executeProtAction(
|
||||||
action: string,
|
action,
|
||||||
ulcoor: string,
|
ulcoor,
|
||||||
brcoor: string,
|
brcoor,
|
||||||
canvasid: number,
|
canvasid,
|
||||||
|
logger = null,
|
||||||
) {
|
) {
|
||||||
if (!ulcoor || !brcoor) {
|
|
||||||
return [403, 'Not all coordinates defined'];
|
|
||||||
}
|
|
||||||
if (!canvasid) {
|
if (!canvasid) {
|
||||||
return [403, 'canvasid not defined'];
|
return [403, 'canvasid not defined'];
|
||||||
}
|
}
|
||||||
|
|
||||||
let splitCoords = ulcoor.trim().split('_');
|
|
||||||
if (splitCoords.length !== 2) {
|
|
||||||
return [403, 'Invalid Coordinate Format for top-left corner'];
|
|
||||||
}
|
|
||||||
const [x, y] = splitCoords.map((z) => Math.floor(Number(z)));
|
|
||||||
splitCoords = brcoor.trim().split('_');
|
|
||||||
if (splitCoords.length !== 2) {
|
|
||||||
return [403, 'Invalid Coordinate Format for bottom-right corner'];
|
|
||||||
}
|
|
||||||
const [u, v] = splitCoords.map((z) => Math.floor(Number(z)));
|
|
||||||
|
|
||||||
const canvas = canvases[canvasid];
|
const canvas = canvases[canvasid];
|
||||||
|
|
||||||
let error = null;
|
let error = null;
|
||||||
if (Number.isNaN(x)) {
|
if (!ulcoor || !brcoor) {
|
||||||
error = 'x of top-left corner is not a valid number';
|
error = 'Not all coordinates defined';
|
||||||
} else if (Number.isNaN(y)) {
|
|
||||||
error = 'y of top-left corner is not a valid number';
|
|
||||||
} else if (Number.isNaN(u)) {
|
|
||||||
error = 'x of bottom-right corner is not a valid number';
|
|
||||||
} else if (Number.isNaN(v)) {
|
|
||||||
error = 'y of bottom-right corner is not a valid number';
|
|
||||||
} else if (u < x || v < y) {
|
|
||||||
error = 'Corner coordinates are alligned wrong';
|
|
||||||
} else if (!action) {
|
|
||||||
error = 'No imageaction given';
|
|
||||||
} else if (!canvas) {
|
} else if (!canvas) {
|
||||||
error = 'Invalid canvas selected';
|
error = 'Invalid canvas selected';
|
||||||
|
} else if (!action) {
|
||||||
|
error = 'No imageaction given';
|
||||||
} else if (action !== 'protect' && action !== 'unprotect') {
|
} else if (action !== 'protect' && action !== 'unprotect') {
|
||||||
error = 'Invalid action (must be protect or unprotect)';
|
error = 'Invalid action (must be protect or unprotect)';
|
||||||
}
|
}
|
||||||
|
@ -216,19 +241,17 @@ export async function executeProtAction(
|
||||||
return [403, error];
|
return [403, error];
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvasMaxXY = canvas.size / 2;
|
const parseCoords = validateCoorRange(ulcoor, brcoor, canvas.size);
|
||||||
const canvasMinXY = -canvasMaxXY;
|
if (typeof parseCoords === 'string') {
|
||||||
if (x < canvasMinXY || y < canvasMinXY
|
return [403, parseCoords];
|
||||||
|| x >= canvasMaxXY || y >= canvasMaxXY) {
|
|
||||||
return [403, 'Coordinates of top-left corner are outside of canvas'];
|
|
||||||
}
|
|
||||||
if (u < canvasMinXY || v < canvasMinXY
|
|
||||||
|| u >= canvasMaxXY || v >= canvasMaxXY) {
|
|
||||||
return [403, 'Coordinates of bottom-right corner are outside of canvas'];
|
|
||||||
}
|
}
|
||||||
|
const [x, y, u, v] = parseCoords;
|
||||||
|
|
||||||
const width = u - x + 1;
|
const width = u - x + 1;
|
||||||
const height = v - y + 1;
|
const height = v - y + 1;
|
||||||
|
if (width * height > 10000000) {
|
||||||
|
return [403, 'Can not set protection to more than 10m pixels at onec'];
|
||||||
|
}
|
||||||
const protect = action === 'protect';
|
const protect = action === 'protect';
|
||||||
const pxlCount = await protectCanvasArea(
|
const pxlCount = await protectCanvasArea(
|
||||||
canvasid,
|
canvasid,
|
||||||
|
@ -238,10 +261,13 @@ export async function executeProtAction(
|
||||||
height,
|
height,
|
||||||
protect,
|
protect,
|
||||||
);
|
);
|
||||||
modtoolsLogger.info(
|
if (logger) {
|
||||||
// eslint-disable-next-line max-len
|
logger(
|
||||||
`ADMINTOOLS: Set protect to ${protect} for ${pxlCount} pixels at ${x} / ${y} with dimension ${width}x${height}`,
|
(protect)
|
||||||
);
|
? `Protect ${width}x${height} area at #${canvas.ident},${x},${y}`
|
||||||
|
: `Unprotect ${width}x${height} area at #${canvas.ident},${x},${y}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
200,
|
200,
|
||||||
(protect)
|
(protect)
|
||||||
|
@ -261,63 +287,36 @@ export async function executeProtAction(
|
||||||
* @return [ret, msg] http status code and message
|
* @return [ret, msg] http status code and message
|
||||||
*/
|
*/
|
||||||
export async function executeRollback(
|
export async function executeRollback(
|
||||||
date: string,
|
date,
|
||||||
ulcoor: string,
|
ulcoor,
|
||||||
brcoor: string,
|
brcoor,
|
||||||
canvasid: number,
|
canvasid,
|
||||||
|
logger = null,
|
||||||
) {
|
) {
|
||||||
if (!ulcoor || !brcoor) {
|
|
||||||
return [403, 'Not all coordinates defined'];
|
|
||||||
}
|
|
||||||
if (!canvasid) {
|
if (!canvasid) {
|
||||||
return [403, 'canvasid not defined'];
|
return [403, 'canvasid not defined'];
|
||||||
}
|
}
|
||||||
|
|
||||||
let splitCoords = ulcoor.trim().split('_');
|
|
||||||
if (splitCoords.length !== 2) {
|
|
||||||
return [403, 'Invalid Coordinate Format for top-left corner'];
|
|
||||||
}
|
|
||||||
const [x, y] = splitCoords.map((z) => Math.floor(Number(z)));
|
|
||||||
splitCoords = brcoor.trim().split('_');
|
|
||||||
if (splitCoords.length !== 2) {
|
|
||||||
return [403, 'Invalid Coordinate Format for bottom-right corner'];
|
|
||||||
}
|
|
||||||
const [u, v] = splitCoords.map((z) => Math.floor(Number(z)));
|
|
||||||
|
|
||||||
const canvas = canvases[canvasid];
|
const canvas = canvases[canvasid];
|
||||||
|
|
||||||
let error = null;
|
let error = null;
|
||||||
if (Number.isNaN(x)) {
|
if (!ulcoor || !brcoor) {
|
||||||
error = 'x of top-left corner is not a valid number';
|
error = 'Not all coordinates defined';
|
||||||
} else if (Number.isNaN(y)) {
|
} else if (!canvas) {
|
||||||
error = 'y of top-left corner is not a valid number';
|
error = 'Invalid canvas selected';
|
||||||
} else if (Number.isNaN(u)) {
|
|
||||||
error = 'x of bottom-right corner is not a valid number';
|
|
||||||
} else if (Number.isNaN(v)) {
|
|
||||||
error = 'y of bottom-right corner is not a valid number';
|
|
||||||
} else if (u < x || v < y) {
|
|
||||||
error = 'Corner coordinates are alligned wrong';
|
|
||||||
} else if (!date) {
|
} else if (!date) {
|
||||||
error = 'No date given';
|
error = 'No date given';
|
||||||
} else if (Number.isNaN(Number(date)) || date.length !== 8) {
|
} else if (Number.isNaN(Number(date)) || date.length !== 8) {
|
||||||
error = 'Invalid date';
|
error = 'Invalid date';
|
||||||
} else if (!canvas) {
|
|
||||||
error = 'Invalid canvas selected';
|
|
||||||
}
|
}
|
||||||
if (error !== null) {
|
if (error !== null) {
|
||||||
return [403, error];
|
return [403, error];
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvasMaxXY = canvas.size / 2;
|
|
||||||
const canvasMinXY = -canvasMaxXY;
|
const parseCoords = validateCoorRange(ulcoor, brcoor, canvas.size);
|
||||||
if (x < canvasMinXY || y < canvasMinXY
|
if (typeof parseCoords === 'string') {
|
||||||
|| x >= canvasMaxXY || y >= canvasMaxXY) {
|
return [403, parseCoords];
|
||||||
return [403, 'Coordinates of top-left corner are outside of canvas'];
|
|
||||||
}
|
|
||||||
if (u < canvasMinXY || v < canvasMinXY
|
|
||||||
|| u >= canvasMaxXY || v >= canvasMaxXY) {
|
|
||||||
return [403, 'Coordinates of bottom-right corner are outside of canvas'];
|
|
||||||
}
|
}
|
||||||
|
const [x, y, u, v] = parseCoords;
|
||||||
|
|
||||||
const width = u - x + 1;
|
const width = u - x + 1;
|
||||||
const height = v - y + 1;
|
const height = v - y + 1;
|
||||||
|
@ -333,10 +332,12 @@ export async function executeRollback(
|
||||||
height,
|
height,
|
||||||
date,
|
date,
|
||||||
);
|
);
|
||||||
modtoolsLogger.info(
|
if (logger) {
|
||||||
|
logger(
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
`ADMINTOOLS: Rollback to ${date} for ${pxlCount} pixels at ${x} / ${y} with dimension ${width}x${height}`,
|
`Rollback to ${date} for ${pxlCount} pixels with dimension ${width}x${height} at #${canvas.ident},${x},${y}`,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
200,
|
200,
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
|
@ -381,6 +382,9 @@ export async function removeMod(userId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function makeMod(name) {
|
export async function makeMod(name) {
|
||||||
|
if (!name) {
|
||||||
|
throw new Error('No username given');
|
||||||
|
}
|
||||||
let user = null;
|
let user = null;
|
||||||
try {
|
try {
|
||||||
user = await RegUser.findOne({
|
user = await RegUser.findOne({
|
||||||
|
|
|
@ -77,6 +77,19 @@ export function getChunkOfPixel(
|
||||||
return [cx, cy];
|
return [cx, cy];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get coordinates of top-left corner of chunk
|
||||||
|
export function getCornerOfChunk(
|
||||||
|
canvasSize,
|
||||||
|
i,
|
||||||
|
j,
|
||||||
|
is3d = false,
|
||||||
|
) {
|
||||||
|
const tileSize = (is3d) ? THREE_TILE_SIZE : TILE_SIZE;
|
||||||
|
const x = (i * tileSize) - (canvasSize / 2);
|
||||||
|
const y = (j * tileSize) - (canvasSize / 2);
|
||||||
|
return [x, y, 0];
|
||||||
|
}
|
||||||
|
|
||||||
export function getTileOfPixel(
|
export function getTileOfPixel(
|
||||||
tileScale,
|
tileScale,
|
||||||
pixel,
|
pixel,
|
||||||
|
|
107
src/data/models/CanvasCleaner.js
Normal file
107
src/data/models/CanvasCleaner.js
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* storing Event data
|
||||||
|
*/
|
||||||
|
|
||||||
|
import redis from '../redis';
|
||||||
|
import logger from '../../core/logger';
|
||||||
|
|
||||||
|
const DATA_KEY = 'clr:dat';
|
||||||
|
const STAT_KEY = 'clr:sta';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gets data of CanvasCleaner from redis
|
||||||
|
* @return Array with [canvasId, x, y, u, v, methodName] (all int except Name)
|
||||||
|
* (check core/CanvasCleaner for the meaning)
|
||||||
|
*/
|
||||||
|
export async function getData() {
|
||||||
|
const data = await redis.getAsync(DATA_KEY);
|
||||||
|
if (data) {
|
||||||
|
const parsedData = data.toString().split(':');
|
||||||
|
for (let i = 0; i < parsedData.length - 1; i += 1) {
|
||||||
|
const num = parseInt(parsedData[i], 10);
|
||||||
|
if (Number.isNaN(num)) {
|
||||||
|
logger.warn(
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
`[CanvasCleaner] ${DATA_KEY} in redis does not seem legit (int conversion).`,
|
||||||
|
);
|
||||||
|
return [0, 0, 0, 0, 0, 0, ''];
|
||||||
|
}
|
||||||
|
parsedData[i] = num;
|
||||||
|
}
|
||||||
|
if (parsedData.length === 6) {
|
||||||
|
return parsedData;
|
||||||
|
}
|
||||||
|
logger.warn(
|
||||||
|
`[CanvasCleaner] ${DATA_KEY} in redis does not seem legit.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return [0, 0, 0, 0, 0, 0, ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Writes data of CanvasCleaner to redis
|
||||||
|
* @param check out core/CanvasCleaner
|
||||||
|
*/
|
||||||
|
export async function setData(canvasId, x, y, u, v, methodName) {
|
||||||
|
const dataStr = `${canvasId}:${x}:${y}:${u}:${v}:${methodName}`;
|
||||||
|
if (
|
||||||
|
Number.isNaN(parseInt(canvasId, 10))
|
||||||
|
|| Number.isNaN(parseInt(x, 10))
|
||||||
|
|| Number.isNaN(parseInt(y, 10))
|
||||||
|
|| Number.isNaN(parseInt(u, 10))
|
||||||
|
|| Number.isNaN(parseInt(v, 10))
|
||||||
|
) {
|
||||||
|
logger.warn(
|
||||||
|
`[CanvasCleaner] can not write ${dataStr} to redis, seems not legit.`,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return redis.setAsync(DATA_KEY, dataStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gets status of CanvasCleaner from redis
|
||||||
|
* @return Array with [cIter, running]
|
||||||
|
* cIter: current chunk iterator integer
|
||||||
|
* running: boolean if filter is running
|
||||||
|
*/
|
||||||
|
export async function getStatus() {
|
||||||
|
const stat = await redis.getAsync(STAT_KEY);
|
||||||
|
if (stat) {
|
||||||
|
const parsedStat = stat.toString().split(':');
|
||||||
|
if (parsedStat.length !== 2) {
|
||||||
|
logger.warn(
|
||||||
|
`[CanvasCleaner] ${STAT_KEY} in redis is incomplete.`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const cIter = parseInt(parsedStat[0], 10);
|
||||||
|
const running = !!parseInt(parsedStat[1], 10);
|
||||||
|
if (!Number.isNaN(cIter)) {
|
||||||
|
return [cIter, running];
|
||||||
|
}
|
||||||
|
logger.warn(
|
||||||
|
`[CanvasCleaner] ${STAT_KEY} in redis does not seem legit.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [0, false];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Writes status of CanvasCleaner to redis
|
||||||
|
* @param cIter current chunk iterator integer
|
||||||
|
* @param running Boolean if running or not
|
||||||
|
*/
|
||||||
|
export async function setStatus(cIter, running) {
|
||||||
|
const runningInt = (running) ? 1 : 0;
|
||||||
|
const statString = `${cIter}:${runningInt}`;
|
||||||
|
if (
|
||||||
|
Number.isNaN(parseInt(cIter, 10))
|
||||||
|
) {
|
||||||
|
logger.warn(
|
||||||
|
`[CanvasCleaner] can not write ${statString} to redis, seems not legit.`,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return redis.setAsync(STAT_KEY, statString);
|
||||||
|
}
|
|
@ -2,7 +2,6 @@
|
||||||
*
|
*
|
||||||
* data saving for hourly events
|
* data saving for hourly events
|
||||||
*
|
*
|
||||||
* @flow
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// its ok if its slow
|
// its ok if its slow
|
||||||
|
@ -101,7 +100,7 @@ export async function clearOldEvent() {
|
||||||
* @param minutes minutes till next event
|
* @param minutes minutes till next event
|
||||||
* @param i, j chunk coordinates of center of event
|
* @param i, j chunk coordinates of center of event
|
||||||
*/
|
*/
|
||||||
export async function setNextEvent(minutes: number, i: number, j: number) {
|
export async function setNextEvent(minutes, i, j) {
|
||||||
await clearOldEvent();
|
await clearOldEvent();
|
||||||
for (let jc = j - 1; jc <= j + 1; jc += 1) {
|
for (let jc = j - 1; jc <= j + 1; jc += 1) {
|
||||||
for (let ic = i - 1; ic <= i + 1; ic += 1) {
|
for (let ic = i - 1; ic <= i + 1; ic += 1) {
|
||||||
|
|
|
@ -10,13 +10,15 @@ import express from 'express';
|
||||||
import type { Request, Response } from 'express';
|
import type { Request, Response } from 'express';
|
||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
|
|
||||||
|
import CanvasCleaner from '../../core/CanvasCleaner';
|
||||||
import { getIPFromRequest } from '../../utils/ip';
|
import { getIPFromRequest } from '../../utils/ip';
|
||||||
import { modtoolsLogger } from '../../core/logger';
|
import logger, { modtoolsLogger } from '../../core/logger';
|
||||||
import {
|
import {
|
||||||
executeIPAction,
|
executeIPAction,
|
||||||
executeImageAction,
|
executeImageAction,
|
||||||
executeProtAction,
|
executeProtAction,
|
||||||
executeRollback,
|
executeRollback,
|
||||||
|
executeCleanerAction,
|
||||||
getModList,
|
getModList,
|
||||||
removeMod,
|
removeMod,
|
||||||
makeMod,
|
makeMod,
|
||||||
|
@ -43,7 +45,7 @@ const upload = multer({
|
||||||
router.use(async (req, res, next) => {
|
router.use(async (req, res, next) => {
|
||||||
const ip = getIPFromRequest(req);
|
const ip = getIPFromRequest(req);
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
modtoolsLogger.info(
|
logger.warn(
|
||||||
`MODTOOLS: ${ip} tried to access modtools without login`,
|
`MODTOOLS: ${ip} tried to access modtools without login`,
|
||||||
);
|
);
|
||||||
const { t } = req.ttag;
|
const { t } = req.ttag;
|
||||||
|
@ -55,16 +57,13 @@ router.use(async (req, res, next) => {
|
||||||
* 2 = Mod
|
* 2 = Mod
|
||||||
*/
|
*/
|
||||||
if (!req.user.userlvl) {
|
if (!req.user.userlvl) {
|
||||||
modtoolsLogger.info(
|
logger.warn(
|
||||||
`MODTOOLS: ${ip} / ${req.user.id} tried to access modtools`,
|
`MODTOOLS: ${ip} / ${req.user.id} tried to access modtools`,
|
||||||
);
|
);
|
||||||
const { t } = req.ttag;
|
const { t } = req.ttag;
|
||||||
res.status(403).send(t`You are not allowed to access this page`);
|
res.status(403).send(t`You are not allowed to access this page`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
modtoolsLogger.info(
|
|
||||||
`MODTOOLS: ${req.user.id} / ${req.user.regUser.name} is using modtools`,
|
|
||||||
);
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
@ -74,7 +73,40 @@ router.use(async (req, res, next) => {
|
||||||
* Post for mod + admin
|
* Post for mod + admin
|
||||||
*/
|
*/
|
||||||
router.post('/', upload.single('image'), async (req, res, next) => {
|
router.post('/', upload.single('image'), async (req, res, next) => {
|
||||||
|
const aLogger = (text) => {
|
||||||
|
const timeString = new Date().toLocaleTimeString();
|
||||||
|
modtoolsLogger.info(
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
`${timeString} | MODTOOLS> ${req.user.regUser.name}[${req.user.id}]> ${text}`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (req.body.cleanerstat) {
|
||||||
|
const ret = CanvasCleaner.reportStatus();
|
||||||
|
res.status(200);
|
||||||
|
res.json(ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (req.body.cleanercancel) {
|
||||||
|
const ret = CanvasCleaner.stop();
|
||||||
|
res.status(200).send(ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (req.body.cleaneraction) {
|
||||||
|
const {
|
||||||
|
cleaneraction, ulcoor, brcoor, canvasid,
|
||||||
|
} = req.body;
|
||||||
|
const [ret, msg] = await executeCleanerAction(
|
||||||
|
cleaneraction,
|
||||||
|
ulcoor,
|
||||||
|
brcoor,
|
||||||
|
canvasid,
|
||||||
|
aLogger,
|
||||||
|
);
|
||||||
|
res.status(ret).send(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (req.body.imageaction) {
|
if (req.body.imageaction) {
|
||||||
const { imageaction, coords, canvasid } = req.body;
|
const { imageaction, coords, canvasid } = req.body;
|
||||||
const [ret, msg] = await executeImageAction(
|
const [ret, msg] = await executeImageAction(
|
||||||
|
@ -82,10 +114,12 @@ router.post('/', upload.single('image'), async (req, res, next) => {
|
||||||
req.file,
|
req.file,
|
||||||
coords,
|
coords,
|
||||||
canvasid,
|
canvasid,
|
||||||
|
aLogger,
|
||||||
);
|
);
|
||||||
res.status(ret).send(msg);
|
res.status(ret).send(msg);
|
||||||
return;
|
return;
|
||||||
} if (req.body.protaction) {
|
}
|
||||||
|
if (req.body.protaction) {
|
||||||
const {
|
const {
|
||||||
protaction, ulcoor, brcoor, canvasid,
|
protaction, ulcoor, brcoor, canvasid,
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
@ -94,10 +128,12 @@ router.post('/', upload.single('image'), async (req, res, next) => {
|
||||||
ulcoor,
|
ulcoor,
|
||||||
brcoor,
|
brcoor,
|
||||||
canvasid,
|
canvasid,
|
||||||
|
aLogger,
|
||||||
);
|
);
|
||||||
res.status(ret).send(msg);
|
res.status(ret).send(msg);
|
||||||
return;
|
return;
|
||||||
} if (req.body.rollback) {
|
}
|
||||||
|
if (req.body.rollback) {
|
||||||
// rollback is date as YYYYMMdd
|
// rollback is date as YYYYMMdd
|
||||||
const {
|
const {
|
||||||
rollback, ulcoor, brcoor, canvasid,
|
rollback, ulcoor, brcoor, canvasid,
|
||||||
|
@ -107,6 +143,7 @@ router.post('/', upload.single('image'), async (req, res, next) => {
|
||||||
ulcoor,
|
ulcoor,
|
||||||
brcoor,
|
brcoor,
|
||||||
canvasid,
|
canvasid,
|
||||||
|
aLogger,
|
||||||
);
|
);
|
||||||
res.status(ret).send(msg);
|
res.status(ret).send(msg);
|
||||||
return;
|
return;
|
||||||
|
@ -135,9 +172,17 @@ router.use(async (req, res, next) => {
|
||||||
* Post just for admin
|
* Post just for admin
|
||||||
*/
|
*/
|
||||||
router.post('/', async (req, res, next) => {
|
router.post('/', async (req, res, next) => {
|
||||||
|
const aLogger = (text) => {
|
||||||
|
logger.info(`ADMIN> ${req.user.regUser.name}[${req.user.id}]> ${text}`);
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (req.body.ipaction) {
|
if (req.body.ipaction) {
|
||||||
const ret = await executeIPAction(req.body.ipaction, req.body.ip);
|
const ret = await executeIPAction(
|
||||||
|
req.body.ipaction,
|
||||||
|
req.body.ip,
|
||||||
|
aLogger,
|
||||||
|
);
|
||||||
res.status(200).send(ret);
|
res.status(200).send(ret);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user