Add parser for current-day pixellog

This commit is contained in:
HF 2022-08-02 17:14:37 +02:00
parent 3c1fb0ead9
commit 86adb162dc
6 changed files with 317 additions and 4 deletions

View File

@ -49,6 +49,7 @@ async function submitWatchAction(
}
const data = new FormData();
data.append('watchaction', action);
data.append('canvasid', canvas);
data.append('ulcoor', tlcoords);
data.append('brcoor', brcoords);
data.append('time', time);

View File

@ -19,6 +19,10 @@ import {
imageABGR2Canvas,
protectCanvasArea,
} from './Image';
import {
getSummaryFromArea,
getPixelsFromArea,
} from './parsePixelLog';
import rollbackCanvasArea from './rollback';
/*
@ -165,6 +169,88 @@ export async function executeImageAction(
}
}
/*
* Check who placed on a canvas area
* @param action if every pixel or summary should be returned
* @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 Object with {info, cols, rows}
*/
export async function executeWatchAction(
action,
ulcoor,
brcoor,
time,
iid,
canvasid,
) {
if (!canvasid) {
return { info: 'canvasid not defined' };
}
const ts = parseInt(time, 10);
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';
} else if (Number.isNaN(ts)) {
error = 'Invalid time given';
}
if (error) {
return { info: error };
}
const parseCoords = validateCoorRange(ulcoor, brcoor, canvas.size);
if (typeof parseCoords === 'string') {
return { info: parseCoords };
}
const [x, y, u, v] = parseCoords;
if (u - x > 1000 || v - y > 1000) {
return { info: 'Cann not watch larger than 1000x1000 area' };
}
if (action === 'summary') {
const ret = await getSummaryFromArea(
canvasid,
x, y, u, v,
time,
iid,
);
if (typeof ret === 'string') {
return { info: ret };
}
return {
info: null,
columns: ['#pxls', 'IID', 'User', 'last', 'clr', 'ts'],
rows: (ret.length > 300) ? ret.slice(-300) : ret,
};
}
if (action === 'all') {
const ret = await getPixelsFromArea(
canvasid,
x, y, u, v,
time,
iid,
);
if (typeof ret === 'string') {
return { info: ret };
}
return {
info: null,
columns: ['IID', 'User', 'last', 'clr', 'ts'],
rows: ret,
};
}
return { info: 'Invalid action given' };
}
/*
* Execute actions for cleaning/filtering canvas
* @param action what to do

188
src/core/parsePixelLog.js Normal file
View File

@ -0,0 +1,188 @@
import fs from 'fs';
import readline from 'readline';
import { PIXELLOGGER_PREFIX } from './logger';
import { getNamesToIds } from '../data/sql/RegUser';
import { getIdsToIps, getIPofIID } from '../data/sql/IPInfo';
import { getIPv6Subnet } from '../utils/ip';
function parseFile(cb) {
const date = new Date();
const year = date.getUTCFullYear();
let month = date.getUTCMonth() + 1;
let day = date.getUTCDate();
if (day < 10) day = `0${day}`;
if (month < 10) month = `0${month}`;
const filename = `${PIXELLOGGER_PREFIX}${year}-${month}-${day}.log`;
return new Promise((resolve, reject) => {
const fileStream = fs.createReadStream(filename);
const rl = readline.createInterface({
input: fileStream,
});
rl.on('line', (line) => cb(line.split(' ')));
rl.on('error', (err) => {
reject(err);
});
rl.on('close', () => {
resolve();
});
});
}
/*
* Get summary of users placing in area of current day
* @param canvasId id of canvas
* @param xUL, yUL, xBR, yBR area of canvs
* @param time timestamp of when to start
* @param iid Limit on one user (optional)
* @return array of parsed pixel log lines
* string if error
*/
export async function getSummaryFromArea(
canvasId,
xUL,
yUL,
xBR,
yBR,
time,
iid,
) {
const ips = {};
const uids = [];
let filterIP = null;
if (iid) {
filterIP = await getIPofIID(iid);
if (!filterIP) {
return 'Could not resolve IID to IP';
}
}
try {
await parseFile((parts) => {
const [ts, ipFull, uid, cid, x, y,, clr] = parts;
// eslint-disable-next-line eqeqeq
if (canvasId == cid
&& ts >= time
&& x >= xUL
&& x <= xBR
&& y >= yUL
&& y <= yBR
) {
const ip = getIPv6Subnet(ipFull);
if (filterIP && ip !== filterIP) {
return;
}
let curVals = ips[ip];
if (!curVals) {
curVals = [0, ip, uid, 0, 0, 0, 0];
ips[ip] = curVals;
uids.push(uid);
}
curVals[0] += 1;
curVals[3] = x;
curVals[4] = y;
curVals[5] = clr;
curVals[6] = ts;
}
});
} catch (err) {
return `Could not parse logfile: ${err.message}`;
}
const uid2Name = await getNamesToIds(uids);
const ipKeys = Object.keys(ips);
const ip2Id = await getIdsToIps(ipKeys);
const rows = [];
for (let i = 0; i < ipKeys.length; i += 1) {
const [pxls, ip, uid, x, y, clr, ts] = ips[ipKeys[i]];
const userMd = (uid && uid2Name[uid])
? `@[${uid2Name[uid]}](${uid})` : 'N/A';
rows.push([
pxls,
ip2Id[ip] || 'N/A',
userMd,
`#d,${x},${y}`,
clr,
ts,
]);
}
return rows;
}
export async function getPixelsFromArea(
canvasId,
xUL,
yUL,
xBR,
yBR,
time,
iid,
maxRows = 300,
) {
const pixels = [];
const uids = [];
const ips = [];
let filterIP = null;
if (iid) {
filterIP = await getIPofIID(iid);
if (!filterIP) {
return 'Could not resolve IID to IP';
}
}
try {
await parseFile((parts) => {
const [ts, ipFull, uid, cid, x, y,, clr] = parts;
// eslint-disable-next-line eqeqeq
if (canvasId == cid
&& ts >= time
&& x >= xUL
&& x <= xBR
&& y >= yUL
&& y <= yBR
) {
const ip = getIPv6Subnet(ipFull);
if (filterIP && ip !== filterIP) {
return;
}
pixels.push([ip, uid, x, y, clr, ts]);
if (!ips.includes(ip)) {
ips.push(ip);
uids.push(uid);
}
}
});
} catch (err) {
return `Could not parse logfile: ${err.message}`;
}
const uid2Name = await getNamesToIds(uids);
const ip2Id = await getIdsToIps(ips);
const pixelF = (pixels.length > 300) ? pixels.slice(maxRows * -1) : pixels;
const rows = [];
for (let i = 0; i < pixelF.length; i += 1) {
const [ip, uid, x, y, clr, ts] = pixelF[i];
const userMd = (uid && uid2Name[uid])
? `@[${uid2Name[uid]}](${uid})` : 'N/A';
const id = ip2Id[ip] || 'N/A';
rows.push([
id,
userMd,
`#d,${x},${y}`,
clr,
ts,
]);
}
return rows;
}

View File

@ -87,15 +87,34 @@ const IPInfo = sequelize.define('IPInfo', {
},
});
export async function getIPofIID(uuid) {
let result = null;
try {
result = IPInfo.findOne({
attributes: ['ip'],
where: { uuid },
});
} catch {
return null;
}
if (result) {
return result.id;
}
return null;
}
export async function getIdsToIps(ips) {
const result = IPInfo.findAll({
const ipToIdMap = {};
if (!ips.length) {
return ipToIdMap;
}
const result = await IPInfo.findAll({
attributes: ['ip', 'uuid'],
where: {
ip: ips,
},
raw: true,
});
const ipToIdMap = {};
result.forEach((obj) => {
ipToIdMap[obj.ip] = obj.uuid;
});

View File

@ -182,14 +182,17 @@ export async function findIdByNameOrId(searchString) {
}
export async function getNamesToIds(ids) {
const result = RegUser.findAll({
const idToNameMap = {};
if (!ids.length) {
return idToNameMap;
}
const result = await RegUser.findAll({
attributes: ['id', 'name'],
where: {
id: ids,
},
raw: true,
});
const idToNameMap = {};
result.forEach((obj) => {
idToNameMap[obj.id] = obj.name;
});

View File

@ -18,6 +18,7 @@ import {
executeProtAction,
executeRollback,
executeCleanerAction,
executeWatchAction,
getModList,
removeMod,
makeMod,
@ -99,6 +100,21 @@ router.post('/', upload.single('image'), async (req, res, next) => {
res.status(200).send(ret);
return;
}
if (req.body.watchaction) {
const {
watchaction, ulcoor, brcoor, time, iid, canvasid,
} = req.body;
const ret = await executeWatchAction(
watchaction,
ulcoor,
brcoor,
time,
iid,
canvasid,
);
res.status(200).json(ret);
return;
}
if (req.body.cleaneraction) {
const {
cleaneraction, ulcoor, brcoor, canvasid,