Move pixelcount into redis
This commit is contained in:
parent
dd757b035e
commit
db4f006f4a
|
@ -394,7 +394,6 @@ export class ChatProvider {
|
||||||
user.ipSub,
|
user.ipSub,
|
||||||
country,
|
country,
|
||||||
);
|
);
|
||||||
console.log(allowed, needProxycheck, name, id, country);
|
|
||||||
if (allowed) {
|
if (allowed) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`${name} / ${user.ip} tried to send chat message but is not allowed`,
|
`${name} / ${user.ip} tried to send chat message but is not allowed`,
|
||||||
|
|
111
src/core/Ranks.js
Normal file
111
src/core/Ranks.js
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* timers and cron for account related actions
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Sequelize from 'sequelize';
|
||||||
|
import RegUser from '../data/sql/RegUser';
|
||||||
|
import {
|
||||||
|
getRanks,
|
||||||
|
resetDailyRanks,
|
||||||
|
saveDailyTop,
|
||||||
|
loadDailyTop,
|
||||||
|
} from '../data/redis/ranks';
|
||||||
|
import socketEvents from '../socket/socketEvents';
|
||||||
|
import logger from './logger';
|
||||||
|
|
||||||
|
import { MINUTE } from './constants';
|
||||||
|
import { DailyCron } from '../utils/cron';
|
||||||
|
|
||||||
|
class Ranks {
|
||||||
|
ranks; // Array
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.updateRanking = this.updateRanking.bind(this);
|
||||||
|
this.resetDailyRanking = this.resetDailyRanking.bind(this);
|
||||||
|
this.prevTop = [];
|
||||||
|
this.ranks = {
|
||||||
|
dailyRanking: [],
|
||||||
|
ranking: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialize() {
|
||||||
|
this.prevTop = await loadDailyTop();
|
||||||
|
await this.updateRanking();
|
||||||
|
setInterval(this.updateRanking, 10 * MINUTE);
|
||||||
|
DailyCron.hook(this.resetDailyRanking);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* take array of {useId: score} and resolve
|
||||||
|
* user informations
|
||||||
|
*/
|
||||||
|
static async populateRanking(rawRanks) {
|
||||||
|
if (!rawRanks.length) {
|
||||||
|
return rawRanks;
|
||||||
|
}
|
||||||
|
const uids = rawRanks.map((r) => r.id);
|
||||||
|
const userData = await RegUser.findAll({
|
||||||
|
attributes: [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
[
|
||||||
|
Sequelize.fn(
|
||||||
|
'DATEDIFF',
|
||||||
|
Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||||
|
Sequelize.col('createdAt'),
|
||||||
|
),
|
||||||
|
'age',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
where: {
|
||||||
|
id: uids,
|
||||||
|
},
|
||||||
|
raw: true,
|
||||||
|
});
|
||||||
|
for (let i = 0; i < userData.length; i += 1) {
|
||||||
|
const { id, name, age } = userData[i];
|
||||||
|
const dat = rawRanks.find((r) => r.id === id);
|
||||||
|
if (dat) {
|
||||||
|
dat.name = name;
|
||||||
|
dat.age = age;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rawRanks;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateRanking() {
|
||||||
|
if (socketEvents.amIImportant()) {
|
||||||
|
// TODO do this only in main shard
|
||||||
|
}
|
||||||
|
// populate dictionaries
|
||||||
|
const ranking = await Ranks.populateRanking(
|
||||||
|
await getRanks(
|
||||||
|
false,
|
||||||
|
1,
|
||||||
|
100,
|
||||||
|
));
|
||||||
|
const dailyRanking = await Ranks.populateRanking(
|
||||||
|
await getRanks(
|
||||||
|
true,
|
||||||
|
1,
|
||||||
|
100,
|
||||||
|
));
|
||||||
|
this.ranks.ranking = ranking;
|
||||||
|
this.ranks.dailyRanking = dailyRanking;
|
||||||
|
}
|
||||||
|
|
||||||
|
async resetDailyRanking() {
|
||||||
|
if (!socketEvents.amIImportant()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.prevTop = await saveDailyTop(this.ranks.dailyRanking);
|
||||||
|
logger.info('Resetting Daily Ranking');
|
||||||
|
await resetDailyRanks();
|
||||||
|
await this.updateRanking();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const rankings = new Ranks();
|
||||||
|
export default rankings;
|
|
@ -12,7 +12,7 @@ import {
|
||||||
setPixelByOffset,
|
setPixelByOffset,
|
||||||
setPixelByCoords,
|
setPixelByCoords,
|
||||||
} from './setPixel';
|
} from './setPixel';
|
||||||
import rankings from './ranking';
|
import rankings from './Ranks';
|
||||||
import canvases from './canvases';
|
import canvases from './canvases';
|
||||||
|
|
||||||
import { THREE_CANVAS_HEIGHT, THREE_TILE_SIZE, TILE_SIZE } from './constants';
|
import { THREE_CANVAS_HEIGHT, THREE_TILE_SIZE, TILE_SIZE } from './constants';
|
||||||
|
@ -144,6 +144,7 @@ export async function drawByOffsets(
|
||||||
/*
|
/*
|
||||||
* validate pixels
|
* validate pixels
|
||||||
*/
|
*/
|
||||||
|
let ranked = canvas.ranked && user.id && pcd;
|
||||||
for (let u = 0; u < pixels.length; u += 1) {
|
for (let u = 0; u < pixels.length; u += 1) {
|
||||||
const [offset, color] = pixels[u];
|
const [offset, color] = pixels[u];
|
||||||
pxlOffsets.push(offset);
|
pxlOffsets.push(offset);
|
||||||
|
@ -180,15 +181,18 @@ export async function drawByOffsets(
|
||||||
throw new Error(8);
|
throw new Error(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* dont rank antarctica */
|
||||||
// eslint-disable-next-line eqeqeq
|
// eslint-disable-next-line eqeqeq
|
||||||
if (canvas.ranked && (canvasId != 0 || y < 14450) && pcd) {
|
if (canvasId == 0 && y > 14450) {
|
||||||
pixels[u].push(true);
|
ranked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[retCode, pxlCnt, wait, coolDown, needProxycheck] = await allowPlace(
|
[retCode, pxlCnt, wait, coolDown, needProxycheck] = await allowPlace(
|
||||||
ip,
|
ip,
|
||||||
user.id,
|
user.id,
|
||||||
|
user.country,
|
||||||
|
ranked,
|
||||||
canvasId,
|
canvasId,
|
||||||
i, j,
|
i, j,
|
||||||
clrIgnore,
|
clrIgnore,
|
||||||
|
@ -198,11 +202,12 @@ export async function drawByOffsets(
|
||||||
);
|
);
|
||||||
|
|
||||||
for (let u = 0; u < pxlCnt; u += 1) {
|
for (let u = 0; u < pxlCnt; u += 1) {
|
||||||
const [offset, color, ranked] = pixels[u];
|
const [offset, color] = pixels[u];
|
||||||
setPixelByOffset(canvasId, color, i, j, offset);
|
setPixelByOffset(canvasId, color, i, j, offset);
|
||||||
if (ranked) {
|
}
|
||||||
rankedPxlCnt += 1;
|
|
||||||
}
|
if (ranked) {
|
||||||
|
rankedPxlCnt = pxlCnt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const duration = Date.now() - startTime;
|
const duration = Date.now() - startTime;
|
||||||
|
@ -219,10 +224,6 @@ export async function drawByOffsets(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rankedPxlCnt) {
|
|
||||||
user.incrementPixelcount(rankedPxlCnt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (retCode !== 13) {
|
if (retCode !== 13) {
|
||||||
curReqIPs.delete(ip);
|
curReqIPs.delete(ip);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
/*
|
|
||||||
* timers and cron for account related actions
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Sequelize from 'sequelize';
|
|
||||||
import sequelize from '../data/sql/sequelize';
|
|
||||||
import RegUser from '../data/sql/RegUser';
|
|
||||||
import { saveDailyTop, loadDailyTop } from '../data/redis/PrevDayTop';
|
|
||||||
import socketEvents from '../socket/socketEvents';
|
|
||||||
import logger from './logger';
|
|
||||||
|
|
||||||
import { MINUTE } from './constants';
|
|
||||||
import { DailyCron } from '../utils/cron';
|
|
||||||
|
|
||||||
class Ranks {
|
|
||||||
ranks; // Array
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.updateRanking = this.updateRanking.bind(this);
|
|
||||||
this.resetDailyRanking = this.resetDailyRanking.bind(this);
|
|
||||||
this.prevTop = [];
|
|
||||||
this.ranks = {
|
|
||||||
dailyRanking: [],
|
|
||||||
ranking: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async initialize() {
|
|
||||||
this.prevTop = await loadDailyTop();
|
|
||||||
await this.updateRanking();
|
|
||||||
setInterval(this.updateRanking, 5 * MINUTE);
|
|
||||||
DailyCron.hook(this.resetDailyRanking);
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateRanking() {
|
|
||||||
if (socketEvents.amIImportant()) {
|
|
||||||
logger.info('Update pixel rankings in SQL');
|
|
||||||
// recalculate ranking column
|
|
||||||
await sequelize.query(
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
'SET @r=0; UPDATE Users SET ranking= @r:= (@r + 1) ORDER BY totalPixels DESC;',
|
|
||||||
);
|
|
||||||
await sequelize.query(
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
'SET @r=0; UPDATE Users SET dailyRanking= @r:= (@r + 1) ORDER BY dailyTotalPixels DESC;',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
logger.info('Get pixel rankings from SQL');
|
|
||||||
}
|
|
||||||
// populate dictionaries
|
|
||||||
const ranking = await RegUser.findAll({
|
|
||||||
attributes: [
|
|
||||||
'id',
|
|
||||||
'name',
|
|
||||||
'totalPixels',
|
|
||||||
'ranking',
|
|
||||||
'dailyRanking',
|
|
||||||
'dailyTotalPixels',
|
|
||||||
[
|
|
||||||
Sequelize.fn(
|
|
||||||
'DATEDIFF',
|
|
||||||
Sequelize.literal('CURRENT_TIMESTAMP'),
|
|
||||||
Sequelize.col('createdAt'),
|
|
||||||
),
|
|
||||||
'age',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
limit: 100,
|
|
||||||
where: {
|
|
||||||
ranking: { [Sequelize.Op.not]: null },
|
|
||||||
},
|
|
||||||
order: ['ranking'],
|
|
||||||
raw: true,
|
|
||||||
});
|
|
||||||
const dailyRanking = await RegUser.findAll({
|
|
||||||
attributes: [
|
|
||||||
'id',
|
|
||||||
'name',
|
|
||||||
'totalPixels',
|
|
||||||
'ranking',
|
|
||||||
'dailyRanking',
|
|
||||||
'dailyTotalPixels',
|
|
||||||
[
|
|
||||||
Sequelize.fn(
|
|
||||||
'DATEDIFF',
|
|
||||||
Sequelize.literal('CURRENT_TIMESTAMP'),
|
|
||||||
Sequelize.col('createdAt'),
|
|
||||||
),
|
|
||||||
'age',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
limit: 100,
|
|
||||||
where: {
|
|
||||||
dailyRanking: { [Sequelize.Op.not]: null },
|
|
||||||
},
|
|
||||||
order: ['dailyRanking'],
|
|
||||||
raw: true,
|
|
||||||
});
|
|
||||||
this.ranks.ranking = ranking;
|
|
||||||
this.ranks.dailyRanking = dailyRanking;
|
|
||||||
}
|
|
||||||
|
|
||||||
async resetDailyRanking() {
|
|
||||||
if (!socketEvents.amIImportant()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.prevTop = await saveDailyTop(this.ranks.dailyRanking);
|
|
||||||
logger.info('Resetting Daily Ranking');
|
|
||||||
await RegUser.update({ dailyTotalPixels: 0 }, { where: {} });
|
|
||||||
await this.updateRanking();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const rankings = new Ranks();
|
|
||||||
export default rankings;
|
|
|
@ -10,8 +10,8 @@ import { QueryTypes, Utils } from 'sequelize';
|
||||||
|
|
||||||
import sequelize from './sql/sequelize';
|
import sequelize from './sql/sequelize';
|
||||||
import { RegUser, Channel, UserBlock } from './sql';
|
import { RegUser, Channel, UserBlock } from './sql';
|
||||||
import { incrementPixelcount } from './sql/RegUser';
|
|
||||||
import { setCoolDown, getCoolDown } from './redis/cooldown';
|
import { setCoolDown, getCoolDown } from './redis/cooldown';
|
||||||
|
import { getUserRanks } from './redis/ranks';
|
||||||
import { getIPv6Subnet } from '../utils/ip';
|
import { getIPv6Subnet } from '../utils/ip';
|
||||||
import { ADMIN_IDS } from '../core/config';
|
import { ADMIN_IDS } from '../core/config';
|
||||||
|
|
||||||
|
@ -50,7 +50,6 @@ export const regUserQueryInclude = [{
|
||||||
class User {
|
class User {
|
||||||
id; // string
|
id; // string
|
||||||
ip; // string
|
ip; // string
|
||||||
wait; // ?number
|
|
||||||
regUser; // Object
|
regUser; // Object
|
||||||
channels; // Object
|
channels; // Object
|
||||||
blocked; // Array
|
blocked; // Array
|
||||||
|
@ -179,13 +178,6 @@ class User {
|
||||||
return getCoolDown(this.ipSub, this.id, canvasId);
|
return getCoolDown(this.ipSub, this.id, canvasId);
|
||||||
}
|
}
|
||||||
|
|
||||||
incrementPixelcount(amount = 1) {
|
|
||||||
const { id } = this;
|
|
||||||
if (id) {
|
|
||||||
incrementPixelcount(id, amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getTotalPixels() {
|
async getTotalPixels() {
|
||||||
const { id } = this;
|
const { id } = this;
|
||||||
if (!id) return 0;
|
if (!id) return 0;
|
||||||
|
@ -210,6 +202,7 @@ class User {
|
||||||
}
|
}
|
||||||
|
|
||||||
async setCountry(country) {
|
async setCountry(country) {
|
||||||
|
this.country = country;
|
||||||
if (this.regUser && this.regUser.flag !== country) {
|
if (this.regUser && this.regUser.flag !== country) {
|
||||||
this.regUser.update({
|
this.regUser.update({
|
||||||
flag: country,
|
flag: country,
|
||||||
|
@ -229,7 +222,7 @@ class User {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserData() {
|
async getUserData() {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
userlvl,
|
userlvl,
|
||||||
|
@ -248,23 +241,25 @@ class User {
|
||||||
name: null,
|
name: null,
|
||||||
mailVerified: false,
|
mailVerified: false,
|
||||||
blockDm: false,
|
blockDm: false,
|
||||||
totalPixels: 0,
|
|
||||||
dailyTotalPixels: 0,
|
|
||||||
ranking: null,
|
|
||||||
dailyRanking: null,
|
|
||||||
mailreg: false,
|
mailreg: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const { regUser } = this;
|
const { regUser } = this;
|
||||||
|
const [
|
||||||
|
totalPixels,
|
||||||
|
dailyTotalPixels,
|
||||||
|
ranking,
|
||||||
|
dailyRanking,
|
||||||
|
] = await getUserRanks(id);
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
name: regUser.name,
|
name: regUser.name,
|
||||||
mailVerified: regUser.mailVerified,
|
mailVerified: regUser.mailVerified,
|
||||||
blockDm: regUser.blockDm,
|
blockDm: regUser.blockDm,
|
||||||
totalPixels: regUser.totalPixels,
|
totalPixels,
|
||||||
dailyTotalPixels: regUser.dailyTotalPixels,
|
dailyTotalPixels,
|
||||||
ranking: regUser.ranking,
|
ranking,
|
||||||
dailyRanking: regUser.dailyRanking,
|
dailyRanking,
|
||||||
mailreg: !!(regUser.password),
|
mailreg: !!(regUser.password),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
/*
|
|
||||||
* saving and loading the top 10 of the previous day
|
|
||||||
*/
|
|
||||||
|
|
||||||
import client from './client';
|
|
||||||
import logger from '../../core/logger';
|
|
||||||
|
|
||||||
const PREV_DAILY_TOP_KEY = 'prevtop';
|
|
||||||
|
|
||||||
/*
|
|
||||||
* saves the top 10 into redis
|
|
||||||
* @param dailyRanking Array of dailyRanking
|
|
||||||
*/
|
|
||||||
export async function saveDailyTop(dailyRanking) {
|
|
||||||
const top10 = dailyRanking.slice(0, 10).map((user) => user.id);
|
|
||||||
const jsonTop = JSON.stringify(top10);
|
|
||||||
logger.info(`Saving current daily top 10 into redis: ${jsonTop}`);
|
|
||||||
await client.set(PREV_DAILY_TOP_KEY, jsonTop);
|
|
||||||
return top10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* load top10 from redis
|
|
||||||
* @return Promis<Array> Array of user IDs of the top 10
|
|
||||||
*/
|
|
||||||
export async function loadDailyTop() {
|
|
||||||
const jsonTop = await client.get(PREV_DAILY_TOP_KEY);
|
|
||||||
logger.info(`Loaded current daily top 10 into redis: ${jsonTop}`);
|
|
||||||
return (jsonTop) ? JSON.parse(jsonTop) : [];
|
|
||||||
}
|
|
|
@ -10,11 +10,12 @@ import { REDIS_URL, SHARD_NAME } from '../../core/config';
|
||||||
|
|
||||||
const scripts = {
|
const scripts = {
|
||||||
placePxl: defineScript({
|
placePxl: defineScript({
|
||||||
NUMBER_OF_KEYS: 5,
|
NUMBER_OF_KEYS: 8,
|
||||||
SCRIPT: fs.readFileSync('./workers/lua/placePixel.lua'),
|
SCRIPT: fs.readFileSync('./workers/lua/placePixel.lua'),
|
||||||
transformArguments(...args) {
|
transformArguments(...args) {
|
||||||
return args.map((a) => ((typeof a === 'string') ? a : a.toString()));
|
return args.map((a) => ((typeof a === 'string') ? a : a.toString()));
|
||||||
},
|
},
|
||||||
|
transformReply(arr) { return arr.map((r) => Number(r)); },
|
||||||
}),
|
}),
|
||||||
allowedChat: defineScript({
|
allowedChat: defineScript({
|
||||||
NUMBER_OF_KEYS: 3,
|
NUMBER_OF_KEYS: 3,
|
||||||
|
@ -22,6 +23,26 @@ const scripts = {
|
||||||
transformArguments(...args) {
|
transformArguments(...args) {
|
||||||
return args.map((a) => ((typeof a === 'string') ? a : a.toString()));
|
return args.map((a) => ((typeof a === 'string') ? a : a.toString()));
|
||||||
},
|
},
|
||||||
|
transformReply(arr) { return arr.map((r) => Number(r)); },
|
||||||
|
}),
|
||||||
|
getUserRanks: defineScript({
|
||||||
|
NUMBER_OF_KEYS: 2,
|
||||||
|
SCRIPT: fs.readFileSync('./workers/lua/getUserRanks.lua'),
|
||||||
|
transformArguments(...args) {
|
||||||
|
return args.map((a) => ((typeof a === 'string') ? a : a.toString()));
|
||||||
|
},
|
||||||
|
transformReply(arr) { return arr.map((r) => Number(r)); },
|
||||||
|
}),
|
||||||
|
zmRankRev: defineScript({
|
||||||
|
NUMBER_OF_KEYS: 1,
|
||||||
|
SCRIPT: fs.readFileSync('./workers/lua/zmRankRev.lua'),
|
||||||
|
transformArguments(key, uids) {
|
||||||
|
return [
|
||||||
|
key,
|
||||||
|
...uids.map((a) => ((typeof a === 'string') ? a : a.toString())),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
transformReply(arr) { return arr.map((r) => Number(r)); },
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,15 +5,18 @@
|
||||||
import client from './client';
|
import client from './client';
|
||||||
import { PREFIX as CAPTCHA_PREFIX } from './captcha';
|
import { PREFIX as CAPTCHA_PREFIX } from './captcha';
|
||||||
import { PREFIX as ALLOWED_PREFIX } from './isAllowedCache';
|
import { PREFIX as ALLOWED_PREFIX } from './isAllowedCache';
|
||||||
|
import { RANKED_KEY, DAILY_RANKED_KEY, DAILY_CRANKED_KEY } from './ranks';
|
||||||
import { CAPTCHA_TIME } from '../../core/config';
|
import { CAPTCHA_TIME } from '../../core/config';
|
||||||
|
|
||||||
const PREFIX = 'cd';
|
const PREFIX = 'cd';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* checks how many of the given pixels can be set,
|
* checks how many of the given pixels can be set,
|
||||||
* sets user cooldown and returns the number of pixels to set
|
* sets user cooldown, increments pixelcount
|
||||||
|
* and returns the number of pixels to set
|
||||||
* @param ip ip of request
|
* @param ip ip of request
|
||||||
* @param id userId
|
* @param id userId
|
||||||
|
* @param ranked boolean if increasing rank
|
||||||
* @param clrIgnore, bcd, pcd, cds incormations about canvas
|
* @param clrIgnore, bcd, pcd, cds incormations about canvas
|
||||||
* @param i, j chunk coordinates
|
* @param i, j chunk coordinates
|
||||||
* @param pxls Array with offsets of pixels
|
* @param pxls Array with offsets of pixels
|
||||||
|
@ -22,6 +25,8 @@ const PREFIX = 'cd';
|
||||||
export default function allowPlace(
|
export default function allowPlace(
|
||||||
ip,
|
ip,
|
||||||
id,
|
id,
|
||||||
|
country,
|
||||||
|
ranked,
|
||||||
canvasId,
|
canvasId,
|
||||||
i, j,
|
i, j,
|
||||||
clrIgnore,
|
clrIgnore,
|
||||||
|
@ -35,9 +40,13 @@ export default function allowPlace(
|
||||||
const ipCdKey = `${PREFIX}:${canvasId}:ip:${ip}`;
|
const ipCdKey = `${PREFIX}:${canvasId}:ip:${ip}`;
|
||||||
const idCdKey = (id) ? `${PREFIX}:${canvasId}:id:${id}` : 'nope';
|
const idCdKey = (id) ? `${PREFIX}:${canvasId}:id:${id}` : 'nope';
|
||||||
const chunkKey = `ch:${canvasId}:${i}:${j}`;
|
const chunkKey = `ch:${canvasId}:${i}:${j}`;
|
||||||
|
const cc = country || 'xx';
|
||||||
|
const rankset = (ranked) ? RANKED_KEY : 'nope';
|
||||||
|
const dailyset = (ranked) ? DAILY_RANKED_KEY : 'nope';
|
||||||
return client.placePxl(
|
return client.placePxl(
|
||||||
isalKey, captKey, ipCdKey, idCdKey, chunkKey,
|
// eslint-disable-next-line max-len
|
||||||
clrIgnore, bcd, pcd, cds,
|
isalKey, captKey, ipCdKey, idCdKey, chunkKey, rankset, dailyset, DAILY_CRANKED_KEY,
|
||||||
|
clrIgnore, bcd, pcd, cds, id, cc,
|
||||||
...pxls,
|
...pxls,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
30
src/data/redis/lua/getUserRanks.lua
Normal file
30
src/data/redis/lua/getUserRanks.lua
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
-- Get ranks of user
|
||||||
|
-- Currently only pixelcounts and rankings
|
||||||
|
-- Keys:
|
||||||
|
-- rankset: 'rank'
|
||||||
|
-- dailyset: 'rankd'
|
||||||
|
-- Args:
|
||||||
|
-- userId
|
||||||
|
-- Returns:
|
||||||
|
-- {
|
||||||
|
-- 1: totalPixels
|
||||||
|
-- 2: dailyPixels
|
||||||
|
-- 3: totalRanking
|
||||||
|
-- 4: dailyRanking
|
||||||
|
-- }
|
||||||
|
local ret = {0, 0, 0, 0}
|
||||||
|
-- get total pixels
|
||||||
|
local cnt = redis.call('zscore', KEYS[1], ARGV[1])
|
||||||
|
if cnt then
|
||||||
|
ret[1] = cnt
|
||||||
|
-- get total rank
|
||||||
|
ret [3] = redis.call('zrevrank', KEYS[1], ARGV[1]) + 1
|
||||||
|
end
|
||||||
|
-- get daily pixels
|
||||||
|
local dcnt = redis.call('zscore', KEYS[2], ARGV[1])
|
||||||
|
if dcnt then
|
||||||
|
ret[2] = dcnt
|
||||||
|
-- get daily rank
|
||||||
|
ret [4] = redis.call('zrevrank', KEYS[2], ARGV[1]) + 1
|
||||||
|
end
|
||||||
|
return ret
|
|
@ -1,18 +1,26 @@
|
||||||
-- Checking requirements and calculating cooldown of user wthin
|
-- Checking requirements for placing pixels, calculating cooldown
|
||||||
-- redis itself. Does not set pixels directly. Pixels are set in batches
|
-- of user and incrementing pixel counts wthin redis itself.
|
||||||
|
-- Does not set pixels directly. Pixels are set in batches
|
||||||
-- in RedisCanvas.js
|
-- in RedisCanvas.js
|
||||||
-- This script will get copied into the dist/workers directory from webpack
|
|
||||||
-- Keys:
|
-- Keys:
|
||||||
-- isAlloweed: 'isal:ip' (proxycheck, blacklist, whitelist)
|
-- isAlloweed: 'isal:ip' (proxycheck, blacklist, whitelist)
|
||||||
-- isHuman 'human:ip' (captcha needed when expired)
|
-- isHuman 'human:ip' captcha needed when expired,
|
||||||
|
-- 'nope' if no captcha should be checked
|
||||||
-- ipCD: 'cd:canvasId:ip:ip'
|
-- ipCD: 'cd:canvasId:ip:ip'
|
||||||
-- uCD: 'cd:canvasId:id:userId'
|
-- uCD: 'cd:canvasId:id:userId'
|
||||||
|
-- 'nope' if not logged in
|
||||||
-- chunk: 'ch:canvasId:i:j'
|
-- chunk: 'ch:canvasId:i:j'
|
||||||
|
-- rankset: 'rank' sorted set of pixelcount
|
||||||
|
-- 'nope' if not increasing ranks
|
||||||
|
-- dailyset: 'rankd' sorted set of daily pixelcount
|
||||||
|
-- countryset: sorted set for country stats
|
||||||
-- Args:
|
-- Args:
|
||||||
-- clrIgnore: integer number of what colors are considered unset
|
-- clrIgnore: integer number of what colors are considered unset
|
||||||
-- bcd: number baseColldown (fixed to cdFactor and 0 if admin)
|
-- bcd: number baseColldown (fixed to cdFactor and 0 if admin)
|
||||||
-- pcd: number set pixel cooldown (fixed to cdFactor and 0 if admin)
|
-- pcd: number set pixel cooldown (fixed to cdFactor and 0 if admin)
|
||||||
-- cds: max cooldown of canvas
|
-- cds: max cooldown of canvas
|
||||||
|
-- userId: '0' if not logged in
|
||||||
|
-- cc country code
|
||||||
-- off1, chonk offset of first pixel
|
-- off1, chonk offset of first pixel
|
||||||
-- off2, chonk offset of second pixel
|
-- off2, chonk offset of second pixel
|
||||||
-- ..., infinie pixels possible
|
-- ..., infinie pixels possible
|
||||||
|
@ -21,7 +29,7 @@
|
||||||
-- 1: pixel return status code (check ui/placePixel.js)
|
-- 1: pixel return status code (check ui/placePixel.js)
|
||||||
-- 2: amount of successfully set pixels
|
-- 2: amount of successfully set pixels
|
||||||
-- 3: total cooldown of user
|
-- 3: total cooldown of user
|
||||||
-- 4: info about placed pixel cooldown (addition of last pixel)
|
-- 4: added cooldown of last pixel
|
||||||
-- 5: if we have to update isAllowed( proxycheck)
|
-- 5: if we have to update isAllowed( proxycheck)
|
||||||
-- }
|
-- }
|
||||||
local ret = {0, 0, 0, 0, 0}
|
local ret = {0, 0, 0, 0, 0}
|
||||||
|
@ -69,9 +77,8 @@ local cli = tonumber(ARGV[1])
|
||||||
local bcd = tonumber(ARGV[2])
|
local bcd = tonumber(ARGV[2])
|
||||||
local pcd = tonumber(ARGV[3])
|
local pcd = tonumber(ARGV[3])
|
||||||
local cds = tonumber(ARGV[4])
|
local cds = tonumber(ARGV[4])
|
||||||
for c = 5,#ARGV do
|
for c = 7,#ARGV do
|
||||||
local off = tonumber(ARGV[c]) * 8
|
local off = tonumber(ARGV[c]) * 8
|
||||||
local clr = tonumber(ARGV[c + 1])
|
|
||||||
-- get color of pixel on canvas
|
-- get color of pixel on canvas
|
||||||
local sclr = redis.call('bitfield', KEYS[5], 'get', 'u8', off)
|
local sclr = redis.call('bitfield', KEYS[5], 'get', 'u8', off)
|
||||||
sclr = sclr[1]
|
sclr = sclr[1]
|
||||||
|
@ -88,19 +95,31 @@ for c = 5,#ARGV do
|
||||||
end
|
end
|
||||||
cd = cd + pxlcd
|
cd = cd + pxlcd
|
||||||
if cd > cds then
|
if cd > cds then
|
||||||
|
-- pixelstack used up
|
||||||
|
-- report difference as last cd
|
||||||
cd = cd - pxlcd
|
cd = cd - pxlcd
|
||||||
pxlcd = cds - cd - pxlcd
|
pxlcd = cds - cd - pxlcd
|
||||||
-- pixelstack used up
|
|
||||||
ret[1] = 9
|
ret[1] = 9
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
pxlcnt = pxlcnt + 1
|
pxlcnt = pxlcnt + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
if pxlcnt > 0 and cd > 0 then
|
if pxlcnt > 0 then
|
||||||
redis.call('set', KEYS[3], '', 'px', cd)
|
-- set cooldown
|
||||||
if KEYS[4] ~= "nope" then
|
if cd > 0 then
|
||||||
redis.call('set', KEYS[4], '', 'px', cd)
|
redis.call('set', KEYS[3], '', 'px', cd)
|
||||||
|
if KEYS[4] ~= "nope" then
|
||||||
|
redis.call('set', KEYS[4], '', 'px', cd)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- increment pixelcount
|
||||||
|
if KEYS[6] ~= 'nope' then
|
||||||
|
redis.call('zincrby', KEYS[6], pxlcnt, ARGV[5])
|
||||||
|
redis.call('zincrby', KEYS[7], pxlcnt, ARGV[5])
|
||||||
|
if ARGV[6] ~= 'xx' then
|
||||||
|
redis.call('zincrby', KEYS[8], pxlcnt, ARGV[6])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
16
src/data/redis/lua/zmRankRev.lua
Normal file
16
src/data/redis/lua/zmRankRev.lua
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
-- Get multiple ranks from sorted set
|
||||||
|
-- Keys:
|
||||||
|
-- set: sorted set
|
||||||
|
-- Args:
|
||||||
|
-- [member,...] dynamic amount of members to look for
|
||||||
|
-- return:
|
||||||
|
-- table with the ranks, 0 for an item with no rank
|
||||||
|
local ret = {}
|
||||||
|
for c = 1,#ARGV do
|
||||||
|
local rank = redis.call('zrevrank', KEYS[1], ARGV[c])
|
||||||
|
if not rank then
|
||||||
|
rank = 0
|
||||||
|
end
|
||||||
|
ret[c] = rank
|
||||||
|
end
|
||||||
|
return ret
|
138
src/data/redis/ranks.js
Normal file
138
src/data/redis/ranks.js
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* counter for daily and total pixels and ranking
|
||||||
|
*/
|
||||||
|
import client from './client';
|
||||||
|
|
||||||
|
import logger from '../../core/logger';
|
||||||
|
|
||||||
|
export const RANKED_KEY = 'rank';
|
||||||
|
export const DAILY_RANKED_KEY = 'rankd';
|
||||||
|
export const DAILY_CRANKED_KEY = 'crankd';
|
||||||
|
const PREV_DAY_TOP_KEY = 'prankd';
|
||||||
|
const DAY_STATS_RANKS_KEY = 'ds';
|
||||||
|
const CDAY_STATS_RANKS_KEY = 'cds';
|
||||||
|
const PREV_DAILY_TOP_KEY = 'prevtop';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get pixelcount and ranking
|
||||||
|
* @param userId
|
||||||
|
* @return [ totalPixels, dailyPixels, totalRanking, dailyRanking ]
|
||||||
|
*/
|
||||||
|
export async function getUserRanks(userId) {
|
||||||
|
const ranks = await client.getUserRanks(
|
||||||
|
RANKED_KEY, DAILY_RANKED_KEY,
|
||||||
|
userId,
|
||||||
|
);
|
||||||
|
return ranks.map((r) => Number(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get userIds by ranks
|
||||||
|
* @param daily integer if using daily or total score
|
||||||
|
* @param start, amount rank to start and end
|
||||||
|
* @return Array of objects with {userId, score, dailyScore} in given range
|
||||||
|
*/
|
||||||
|
export async function getRanks(daily, start, amount) {
|
||||||
|
start -= 1;
|
||||||
|
amount -= 1;
|
||||||
|
let key;
|
||||||
|
let valueName;
|
||||||
|
let rankName;
|
||||||
|
let oKey;
|
||||||
|
let oValueName;
|
||||||
|
let oRankName;
|
||||||
|
if (daily) {
|
||||||
|
key = DAILY_RANKED_KEY;
|
||||||
|
valueName = 'dailyTotalPixels';
|
||||||
|
rankName = 'dailyRanking';
|
||||||
|
oKey = RANKED_KEY;
|
||||||
|
oValueName = 'totalPixels';
|
||||||
|
oRankName = 'ranking';
|
||||||
|
} else {
|
||||||
|
key = RANKED_KEY;
|
||||||
|
valueName = 'totalPixels';
|
||||||
|
rankName = 'ranking';
|
||||||
|
oKey = DAILY_RANKED_KEY;
|
||||||
|
oValueName = 'dailyTotalPixels';
|
||||||
|
oRankName = 'dailyRanking';
|
||||||
|
}
|
||||||
|
/* returns { value: uid, score: pixelCnt } */
|
||||||
|
const ranks = await client.zRangeWithScores(key, start, start + amount, {
|
||||||
|
REV: true,
|
||||||
|
});
|
||||||
|
const uids = ranks.map((r) => r.value);
|
||||||
|
if (!uids.length) {
|
||||||
|
return uids;
|
||||||
|
}
|
||||||
|
const oScores = await client.zmScore(oKey, uids);
|
||||||
|
/* impolemented with lua, which blocks :( */
|
||||||
|
const oRanks = await client.zmRankRev(oKey, uids);
|
||||||
|
const ret = [];
|
||||||
|
for (let i = 0; i < ranks.length; i += 1) {
|
||||||
|
const uob = {
|
||||||
|
id: Number(uids[i]),
|
||||||
|
[valueName]: ranks[i].score,
|
||||||
|
[rankName]: i + 1,
|
||||||
|
[oValueName]: oScores[i],
|
||||||
|
[oRankName]: oRanks[i] + 1,
|
||||||
|
};
|
||||||
|
ret.push(uob);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* reset daily ranks
|
||||||
|
* @return boolean for success
|
||||||
|
*/
|
||||||
|
export async function resetDailyRanks() {
|
||||||
|
// store top 10
|
||||||
|
console.log('DAILYREDIS SAVE TOP 10');
|
||||||
|
await client.zRangeWithScores(PREV_DAY_TOP_KEY, DAILY_RANKED_KEY, 0, 9, {
|
||||||
|
REV: true,
|
||||||
|
WITHSCORES: true,
|
||||||
|
});
|
||||||
|
// store day
|
||||||
|
const yesterday = new Date(Date.now() - 1000 * 3600 * 24);
|
||||||
|
let day = yesterday.getUTCDate();
|
||||||
|
if (day < 10) day = `0${day}`;
|
||||||
|
let month = yesterday.getUTCMonth() + 1;
|
||||||
|
if (month < 10) month = `0${month}`;
|
||||||
|
const year = yesterday.getUTCFullYear();
|
||||||
|
const dateKey = `${year}${month}${day}`;
|
||||||
|
console.log('DAILYREDIS', dateKey);
|
||||||
|
await client.zUnionStore(
|
||||||
|
`${DAY_STATS_RANKS_KEY}:${dateKey}`,
|
||||||
|
DAILY_RANKED_KEY,
|
||||||
|
);
|
||||||
|
await client.zUnionStore(
|
||||||
|
`${CDAY_STATS_RANKS_KEY}:${dateKey}`,
|
||||||
|
DAILY_CRANKED_KEY,
|
||||||
|
);
|
||||||
|
// reset daily counter
|
||||||
|
console.log('DAILYREDIS RESET');
|
||||||
|
await client.del(DAILY_RANKED_KEY);
|
||||||
|
await client.del(DAILY_CRANKED_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* saves the top 10 into redis
|
||||||
|
* @param dailyRanking Array of dailyRanking
|
||||||
|
*/
|
||||||
|
export async function saveDailyTop(dailyRanking) {
|
||||||
|
const top10 = dailyRanking.slice(0, 10).map((user) => user.id);
|
||||||
|
const jsonTop = JSON.stringify(top10);
|
||||||
|
logger.info(`Saving current daily top 10 into redis: ${jsonTop}`);
|
||||||
|
await client.set(PREV_DAILY_TOP_KEY, jsonTop);
|
||||||
|
return top10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* load top10 from redis
|
||||||
|
* @return Promis<Array> Array of user IDs of the top 10
|
||||||
|
*/
|
||||||
|
export async function loadDailyTop() {
|
||||||
|
const jsonTop = await client.get(PREV_DAILY_TOP_KEY);
|
||||||
|
logger.info(`Loaded current daily top 10 into redis: ${jsonTop}`);
|
||||||
|
return (jsonTop) ? JSON.parse(jsonTop) : [];
|
||||||
|
}
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
import { DataTypes, QueryTypes } from 'sequelize';
|
import { DataTypes, QueryTypes } from 'sequelize';
|
||||||
|
|
||||||
import logger from '../../core/logger';
|
|
||||||
import sequelize from './sequelize';
|
import sequelize from './sequelize';
|
||||||
import { generateHash } from '../../utils/hash';
|
import { generateHash } from '../../utils/hash';
|
||||||
|
|
||||||
|
@ -42,28 +41,6 @@ const RegUser = sequelize.define('User', {
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
totalPixels: {
|
|
||||||
type: DataTypes.INTEGER.UNSIGNED,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
dailyTotalPixels: {
|
|
||||||
type: DataTypes.INTEGER.UNSIGNED,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
ranking: {
|
|
||||||
type: DataTypes.INTEGER.UNSIGNED,
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
dailyRanking: {
|
|
||||||
type: DataTypes.INTEGER.UNSIGNED,
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
// mail and Minecraft verified
|
// mail and Minecraft verified
|
||||||
verified: {
|
verified: {
|
||||||
type: DataTypes.TINYINT,
|
type: DataTypes.TINYINT,
|
||||||
|
@ -204,59 +181,4 @@ export async function getNamesToIds(ids) {
|
||||||
return idToNameMap;
|
return idToNameMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* increment user pixelcount in batches sequentially
|
|
||||||
* Queue directly accesses queuedPxlIncrement in user object
|
|
||||||
*/
|
|
||||||
let incrementQueue = {};
|
|
||||||
let pushLoop = null;
|
|
||||||
const incrementLoop = async () => {
|
|
||||||
const idKeys = Object.keys(incrementQueue);
|
|
||||||
if (!idKeys.length) {
|
|
||||||
pushLoop = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let queue = incrementQueue;
|
|
||||||
incrementQueue = {};
|
|
||||||
const orderedCnts = {};
|
|
||||||
idKeys.forEach((id) => {
|
|
||||||
const cnt = queue[id];
|
|
||||||
const cntArr = orderedCnts[cnt];
|
|
||||||
if (cntArr) {
|
|
||||||
cntArr.push(id);
|
|
||||||
} else {
|
|
||||||
orderedCnts[cnt] = [id];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
queue = Object.keys(orderedCnts);
|
|
||||||
for (let u = 0; u < queue.length; u += 1) {
|
|
||||||
const by = queue[u];
|
|
||||||
const id = orderedCnts[by];
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await RegUser.increment(['totalPixels', 'dailyTotalPixels'], {
|
|
||||||
by,
|
|
||||||
where: {
|
|
||||||
id,
|
|
||||||
},
|
|
||||||
silent: true,
|
|
||||||
raw: true,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
logger.warn(`Error on pixel increment: ${err.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pushLoop = setTimeout(incrementLoop, 250);
|
|
||||||
};
|
|
||||||
export async function incrementPixelcount(id, by) {
|
|
||||||
if (incrementQueue[id]) {
|
|
||||||
incrementQueue[id] += by;
|
|
||||||
} else {
|
|
||||||
incrementQueue[id] = by;
|
|
||||||
}
|
|
||||||
if (!pushLoop) {
|
|
||||||
pushLoop = setTimeout(incrementLoop, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RegUser;
|
export default RegUser;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* send global ranking
|
* send global ranking
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import rankings from '../core/ranking';
|
import rankings from '../core/Ranks';
|
||||||
|
|
||||||
export default (req, res) => {
|
export default (req, res) => {
|
||||||
res.json(rankings.ranks);
|
res.json(rankings.ranks);
|
||||||
|
|
|
@ -9,7 +9,7 @@ import http from 'http';
|
||||||
|
|
||||||
import forceGC from './core/forceGC';
|
import forceGC from './core/forceGC';
|
||||||
import logger from './core/logger';
|
import logger from './core/logger';
|
||||||
import rankings from './core/ranking';
|
import rankings from './core/Ranks';
|
||||||
import sequelize from './data/sql/sequelize';
|
import sequelize from './data/sql/sequelize';
|
||||||
import { connect as connectRedis } from './data/redis/client';
|
import { connect as connectRedis } from './data/redis/client';
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
|
|
|
@ -51,6 +51,9 @@ export default function ranks(
|
||||||
|
|
||||||
case 's/REC_ME':
|
case 's/REC_ME':
|
||||||
case 's/LOGIN': {
|
case 's/LOGIN': {
|
||||||
|
if (!action.totalPixels) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
const {
|
const {
|
||||||
totalPixels,
|
totalPixels,
|
||||||
dailyTotalPixels,
|
dailyTotalPixels,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user