diff --git a/src/core/draw.js b/src/core/draw.js index e70bd9c..f59da99 100644 --- a/src/core/draw.js +++ b/src/core/draw.js @@ -7,7 +7,7 @@ import { } from './utils'; import logger, { pixelLogger } from './logger'; import RedisCanvas from '../data/redis/RedisCanvas'; -import allowPlace from '../data/redis/allowPlace'; +import allowPlace from '../data/redis/cooldown'; import { setPixelByOffset, setPixelByCoords, @@ -70,7 +70,7 @@ export async function drawByOffsets( let pxlCnt = 0; let rankedPxlCnt = 0; let needProxycheck = 0; - const { ip } = user; + const { ipSub: ip } = user; try { const startTime = Date.now(); @@ -220,7 +220,7 @@ export async function drawByOffsets( } if (rankedPxlCnt) { - await user.incrementPixelcount(rankedPxlCnt); + user.incrementPixelcount(rankedPxlCnt); } if (retCode !== 13) { diff --git a/src/data/User.js b/src/data/User.js index 19fde00..1a0b8e2 100644 --- a/src/data/User.js +++ b/src/data/User.js @@ -7,11 +7,11 @@ * */ import { QueryTypes, Utils } from 'sequelize'; -import redis from './redis/client'; -import logger from '../core/logger'; import sequelize from './sql/sequelize'; import { RegUser, Channel, UserBlock } from './sql'; +import { incrementPixelcount } from './sql/RegUser'; +import { setCoolDown, getCoolDown } from './redis/cooldown'; import { getIPv6Subnet } from '../utils/ip'; import { ADMIN_IDS } from '../core/config'; @@ -171,45 +171,16 @@ class User { return (this.regUser) ? this.regUser.name : null; } - async setWait(wait, canvasId) { - // PX is milliseconds expire - await redis.set(`cd:${canvasId}:ip:${this.ipSub}`, '', { - PX: wait, - }); - if (this.id) { - await redis.set(`cd:${canvasId}:id:${this.id}`, '', { - PX: wait, - }); - } - return true; + setWait(wait, canvasId) { + return setCoolDown(this.ipSub, this.id, canvasId, wait); } - async getWait(canvasId) { - let ttl = await redis.pTTL(`cd:${canvasId}:ip:${this.ipSub}`); - if (this.id) { - const ttlid = await redis.pTTL( - `cd:${canvasId}:id:${this.id}`, - ); - ttl = Math.max(ttl, ttlid); - } - logger.debug('ererer', ttl, typeof ttl); - - const wait = ttl < 0 ? 0 : ttl; - return wait; + getWait(canvasId) { + return getCoolDown(this.ipSub, this.id, canvasId); } - async incrementPixelcount(amount = 1) { - const { id } = this; - if (!id) return false; - try { - await this.regUser.increment( - ['totalPixels', 'dailyTotalPixels'], - { by: amount }, - ); - } catch (err) { - return false; - } - return true; + incrementPixelcount(amount = 1) { + incrementPixelcount(this.regUser, amount); } async getTotalPixels() { diff --git a/src/data/redis/allowPlace.js b/src/data/redis/allowPlace.js deleted file mode 100644 index f1426bb..0000000 --- a/src/data/redis/allowPlace.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * redis script for user pixel placement - * this does not set any pixels itself, see lua/placePixel.lua - */ -import client from './client'; -import { getIPv6Subnet } from '../../utils/ip'; -import { PREFIX as CAPTCHA_PREFIX } from './captcha'; -import { PREFIX as ALLOWED_PREFIX } from './isAllowedCache'; -import { CAPTCHA_TIME } from '../../core/config'; - -/* - * gets pixels and chunk coords and checks if - * and how many a user can set and sets the cooldown accordingly - * @param ip ip of request - * @param id userId - * @param clrIgnore, bcd, pcd, cds incormations about canvas - * @param i, j chunk coordinates - * @param pxls Array with offsets of pixels - * @return see lua/placePixel.lua - */ -export default function allowPlace( - ip, - id, - canvasId, - i, j, - clrIgnore, - bcd, - pcd, - cds, - pxls, -) { - const ipn = getIPv6Subnet(ip); - const isalKey = `${ALLOWED_PREFIX}:${ipn}`; - const captKey = (CAPTCHA_TIME >= 0) ? `${CAPTCHA_PREFIX}:${ipn}` : 'nope'; - const ipCdKey = `cd:${canvasId}:ip:${ipn}`; - const idCdKey = (id) ? `cd:${canvasId}:id:${id}` : 'nope'; - const chunkKey = `ch:${canvasId}:${i}:${j}`; - return client.placePxl( - isalKey, captKey, ipCdKey, idCdKey, chunkKey, - clrIgnore, bcd, pcd, cds, - ...pxls, - ); -} diff --git a/src/data/redis/cooldown.js b/src/data/redis/cooldown.js new file mode 100644 index 0000000..999f758 --- /dev/null +++ b/src/data/redis/cooldown.js @@ -0,0 +1,90 @@ +/* + * redis script for cooldown calculation + * this does not set any pixels itself, see lua/placePixel.lua + */ +import client from './client'; +import { PREFIX as CAPTCHA_PREFIX } from './captcha'; +import { PREFIX as ALLOWED_PREFIX } from './isAllowedCache'; +import { CAPTCHA_TIME } from '../../core/config'; + +const PREFIX = 'cd'; + +/* + * checks how many of the given pixels can be set, + * sets user cooldown and returns the number of pixels to set + * @param ip ip of request + * @param id userId + * @param clrIgnore, bcd, pcd, cds incormations about canvas + * @param i, j chunk coordinates + * @param pxls Array with offsets of pixels + * @return see lua/placePixel.lua + */ +export default function allowPlace( + ip, + id, + canvasId, + i, j, + clrIgnore, + bcd, + pcd, + cds, + pxls, +) { + const isalKey = `${ALLOWED_PREFIX}:${ip}`; + const captKey = (CAPTCHA_TIME >= 0) ? `${CAPTCHA_PREFIX}:${ip}` : 'nope'; + const ipCdKey = `${PREFIX}:${canvasId}:ip:${ip}`; + const idCdKey = (id) ? `${PREFIX}:${canvasId}:id:${id}` : 'nope'; + const chunkKey = `ch:${canvasId}:${i}:${j}`; + return client.placePxl( + isalKey, captKey, ipCdKey, idCdKey, chunkKey, + clrIgnore, bcd, pcd, cds, + ...pxls, + ); +} + +/* + * get cooldown of specific user + * @param ip ip of request + * @param id userId + * @param canvasId + * @return cooldown + */ +export async function getCoolDown( + ip, + id, + canvasId, +) { + let ttl = await client.pTTL(`${PREFIX}:${canvasId}:ip:${ip}`); + if (id) { + const ttlid = await client.pTTL(`${PREFIX}:${canvasId}:id:${id}`); + ttl = Math.max(ttl, ttlid); + } + const cooldown = ttl < 0 ? 0 : ttl; + return cooldown; +} + +/* + * set cooldown of specific user + * @param ip ip of request + * @param id userId + * @param canvasId + * @param cooldown (in ms) + * @return cooldown + */ +export async function setCoolDown( + ip, + id, + canvasId, + cooldown, +) { + // PX is milliseconds expire + await client.set(`${PREFIX}:${canvasId}:ip:${ip}`, '', { + PX: cooldown, + }); + if (id) { + await client.set(`${PREFIX}:${canvasId}:id:${id}`, '', { + PX: cooldown, + }); + } + return true; +} diff --git a/src/data/sql/RegUser.js b/src/data/sql/RegUser.js index 4e157df..0d64906 100644 --- a/src/data/sql/RegUser.js +++ b/src/data/sql/RegUser.js @@ -6,8 +6,9 @@ */ import { DataTypes, QueryTypes } from 'sequelize'; -import sequelize from './sequelize'; +import logger from '../../core/logger'; +import sequelize from './sequelize'; import { generateHash } from '../../utils/hash'; @@ -203,4 +204,42 @@ export async function getNamesToIds(ids) { return idToNameMap; } +/* + * increment user pixelcount in a batched transaction + */ +const incrementQueue = []; +let pushLoop = null; +const incrementLoop = async () => { + if (!incrementQueue.length) { + clearInterval(pushLoop); + pushLoop = null; + } + try { + sequelize.transaction(async (t) => { + while (incrementQueue.length) { + const [model, amount] = incrementQueue.pop(); + // eslint-disable-next-line no-await-in-loop + await model.increment( + ['totalPixels', 'dailyTotalPixels'], + { by: amount, transaction: t }, + ); + } + return true; + }); + } catch (err) { + logger.warn(`Error on batched incrementing pixelcounts: ${err.message}`); + } +}; +// TODO remove this after testing +setInterval(() => { + // eslint-disable-next-line no-console + console.log('INCREMENTATION QUEUE SIZE', incrementQueue.length); +}, 300000); +export async function incrementPixelcount(model, amount) { + incrementQueue.push([model, amount]); + if (!pushLoop) { + pushLoop = setInterval(incrementLoop, 250); + } +} + export default RegUser;