add /mute chat command

fix eslint no-import error for jsons that don't get packed by webpack (i am sure there must be a better way to do that, but i am too lazy right now)
This commit is contained in:
HF 2020-04-27 18:24:57 +02:00
parent cd7c373277
commit 5cc626b8a0
18 changed files with 169 additions and 49 deletions

View File

@ -24,6 +24,7 @@ import {
createPngBackup, createPngBackup,
incrementialBackupRedis, incrementialBackupRedis,
} from './core/tilesBackup'; } from './core/tilesBackup';
// eslint-disable-next-line import/no-unresolved
import canvases from './canvases.json'; import canvases from './canvases.json';
/* /*

View File

@ -48,7 +48,7 @@ const HelpModal = () => (
Some canvases have a different cooldown for replacing a user-set pixels than placing on a unset pixel. i.e. 4s/7s means 4s on fresh Some canvases have a different cooldown for replacing a user-set pixels than placing on a unset pixel. i.e. 4s/7s means 4s on fresh
pixels and 7s on already set pixels. pixels and 7s on already set pixels.
Higher zoomlevels take some time to update, the 3D globe gets updated at least once per day. Higher zoomlevels take some time to update, the 3D globe gets updated at least once per day.
Have fun!</p> Have fun!</p>
<p>Discord: <a href="./discord" target="_blank">pixelplanet.fun/discord</a></p> <p>Discord: <a href="./discord" target="_blank">pixelplanet.fun/discord</a></p>
<p>Source on <a href="https://github.com/pixelplanetdev/pixelplanet" target="_blank">github</a></p> <p>Source on <a href="https://github.com/pixelplanetdev/pixelplanet" target="_blank">github</a></p>
<p>Reddit: <a href="https://www.reddit.com/r/PixelPlanetFun/" target="_blank">r/PixelPlanetFun</a></p> <p>Reddit: <a href="https://www.reddit.com/r/PixelPlanetFun/" target="_blank">r/PixelPlanetFun</a></p>

View File

@ -79,7 +79,7 @@ class UserMessages extends React.Component {
{(messages.includes('not_verified') && messages.splice(messages.indexOf('not_verified'), 1)) {(messages.includes('not_verified') && messages.splice(messages.indexOf('not_verified'), 1))
? ( ? (
<p className="usermessages"> <p className="usermessages">
Please verify your mail address or your account could get deleted after a few days. Please verify your mail address or your account could get deleted after a few days.
{(this.state.verify_answer) {(this.state.verify_answer)
? <span className="modallink">{this.state.verify_answer}</span> ? <span className="modallink">{this.state.verify_answer}</span>
: <span className="modallink" onClick={this.submit_resend_verify}>Click here to request a new verification mail.</span>} : <span className="modallink" onClick={this.submit_resend_verify}>Click here to request a new verification mail.</span>}

114
src/core/ChatProvider.js Normal file
View File

@ -0,0 +1,114 @@
/* @flow */
import logger from '../core/logger';
import redis from '../data/redis';
import User from '../data/models/User';
import webSockets from '../socket/websockets';
class ChatProvider {
/*
* TODO:
* history really be saved in redis
*/
history: Array;
constructor() {
this.history = [];
}
addMessage(name, message) {
if (this.history.length > 20) {
this.history.shift();
}
this.history.push([name, message]);
}
async sendMessage(user, message) {
if (message.length > 300) {
// eslint-disable-next-line max-len
return 'You can\'t send a message this long :(';
}
const name = (user.regUser) ? user.regUser.name : null;
if (!name) {
// eslint-disable-next-line max-len
return 'Couldn\'t send your message, pls log out and back in again.';
}
if (user.isAdmin() && message.charAt(0) === '/') {
// admin commands
const cmd = message.split(' ', 3);
if (cmd[0] === '/mute') {
return ChatProvider.mute(cmd[1], cmd[2]);
} if (cmd[0] === '/unmute') {
return ChatProvider.unmute(cmd[1]);
}
}
const muted = await ChatProvider.checkIfMuted(user);
if (muted === -1) {
return 'You are permanently muted, join our discord to apppeal the mute';
}
if (muted > 0) {
if (muted > 120) {
const timeMin = Math.round(muted / 60);
return `You are muted for another ${timeMin} minutes`;
}
return `You are muted for another ${muted} seconds`;
}
this.addMessage(name, message);
webSockets.broadcastChatMessage(name, message);
return null;
}
broadcastChatMessage(name, message, sendapi: boolean = true) {
this.addMessage(name, message);
webSockets.broadcastChatMessage(name, message, sendapi);
}
static async checkIfMuted(user) {
const key = `mute:${user.id}`;
const ttl: number = await redis.ttlAsync(key);
return ttl;
}
static async mute(name, timeMin) {
const id = await User.name2Id(name);
if (!id) {
return `Couldn't find user ${name}`;
}
const key = `mute:${id}`;
if (timeMin) {
const ttl = timeMin * 60;
await redis.setAsync(key, '', 'EX', ttl);
webSockets.broadcastChatMessage('info',
`${name} has been muted for ${timeMin}min`);
} else {
await redis.setAsync(key, '');
webSockets.broadcastChatMessage('info',
`${name} has been muted forever`);
}
logger.info(`Muted user ${id}`);
return null;
}
static async unmute(name) {
const id = await User.name2Id(name);
if (!id) {
return `Couldn't find user ${name}`;
}
const key = `mute:${id}`;
const delKeys = await redis.delAsync(key);
if (delKeys !== 1) {
return `User ${name} is not muted`;
}
webSockets.broadcastChatMessage('info',
`${name} has been unmuted`);
logger.info(`Unmuted user ${id}`);
return null;
}
}
const chatProvider = new ChatProvider();
export default chatProvider;

View File

@ -8,6 +8,7 @@ import RedisCanvas from '../data/models/RedisCanvas';
import logger from './logger'; import logger from './logger';
import { getChunkOfPixel } from './utils'; import { getChunkOfPixel } from './utils';
import { TILE_SIZE } from './constants'; import { TILE_SIZE } from './constants';
// eslint-disable-next-line import/no-unresolved
import canvases from './canvases.json'; import canvases from './canvases.json';
import Palette from './Palette'; import Palette from './Palette';

View File

@ -8,6 +8,7 @@ import { getChunkOfPixel, getOffsetOfPixel } from './utils';
import webSockets from '../socket/websockets'; import webSockets from '../socket/websockets';
import logger from './logger'; import logger from './logger';
import RedisCanvas from '../data/models/RedisCanvas'; import RedisCanvas from '../data/models/RedisCanvas';
// eslint-disable-next-line import/no-unresolved
import canvases from './canvases.json'; import canvases from './canvases.json';
import { THREE_CANVAS_HEIGHT } from './constants'; import { THREE_CANVAS_HEIGHT } from './constants';

View File

@ -5,6 +5,7 @@
* *
* @flow * @flow
*/ */
// eslint-disable-next-line import/no-unresolved
import canvases from './canvases.json'; import canvases from './canvases.json';

View File

@ -33,6 +33,7 @@ passport.deserializeUser((req, id, done) => {
RegUser.findOne({ where: { id } }).then((reguser) => { RegUser.findOne({ where: { id } }).then((reguser) => {
if (reguser) { if (reguser) {
user.regUser = reguser; user.regUser = reguser;
user.id = id;
} else { } else {
user.id = null; user.id = null;
} }

View File

@ -8,6 +8,7 @@ import fs from 'fs';
import type { Cell } from './Cell'; import type { Cell } from './Cell';
import logger from './logger'; import logger from './logger';
// eslint-disable-next-line import/no-unresolved
import canvases from './canvases.json'; import canvases from './canvases.json';
import Palette from './Palette'; import Palette from './Palette';
import RedisCanvas from '../data/models/RedisCanvas'; import RedisCanvas from '../data/models/RedisCanvas';

View File

@ -6,6 +6,7 @@ import {
THREE_TILE_SIZE, THREE_TILE_SIZE,
THREE_CANVAS_HEIGHT, THREE_CANVAS_HEIGHT,
} from '../../core/constants'; } from '../../core/constants';
// eslint-disable-next-line import/no-unresolved
import canvases from './canvases.json'; import canvases from './canvases.json';
import logger from '../../core/logger'; import logger from '../../core/logger';

View File

@ -9,7 +9,6 @@
import Sequelize from 'sequelize'; import Sequelize from 'sequelize';
import redis from '../redis'; import redis from '../redis';
import { randomDice } from '../../utils/random';
import logger from '../../core/logger'; import logger from '../../core/logger';
import Model from '../sequelize'; import Model from '../sequelize';
@ -32,8 +31,23 @@ class User {
this.regUser = null; this.regUser = null;
} }
static async name2Id(name: string) {
try {
const userq = await Model.query('SELECT id FROM Users WHERE name = $1',
{
bind: [name],
type: Sequelize.QueryTypes.SELECT,
raw: true,
plain: true,
});
return userq.id;
} catch {
return null;
}
}
async setWait(coolDown: number, canvasId: number): Promise<boolean> { async setWait(coolDown: number, canvasId: number): Promise<boolean> {
if (coolDown == 0) return false; if (!coolDown) return false;
this.wait = Date.now() + coolDown; this.wait = Date.now() + coolDown;
// PX is milliseconds expire // PX is milliseconds expire
await redis.setAsync(`cd:${canvasId}:ip:${this.ip}`, '', 'PX', coolDown); await redis.setAsync(`cd:${canvasId}:ip:${this.ip}`, '', 'PX', coolDown);
@ -46,7 +60,9 @@ class User {
async getWait(canvasId: number): Promise<?number> { async getWait(canvasId: number): Promise<?number> {
let ttl: number = await redis.pttlAsync(`cd:${canvasId}:ip:${this.ip}`); let ttl: number = await redis.pttlAsync(`cd:${canvasId}:ip:${this.ip}`);
if (this.id != null && ttl < 0) { if (this.id != null && ttl < 0) {
const ttlid: number = await redis.pttlAsync(`cd:${canvasId}:id:${this.id}`); const ttlid: number = await redis.pttlAsync(
`cd:${canvasId}:id:${this.id}`,
);
ttl = Math.max(ttl, ttlid); ttl = Math.max(ttl, ttlid);
} }
logger.debug('ererer', ttl, typeof ttl); logger.debug('ererer', ttl, typeof ttl);
@ -82,10 +98,15 @@ class User {
return this.regUser.totalPixels; return this.regUser.totalPixels;
} }
try { try {
const userq = await Model.query('SELECT totalPixels FROM Users WHERE id = $1', const userq = await Model.query(
'SELECT totalPixels FROM Users WHERE id = $1',
{ {
bind: [id], type: Sequelize.QueryTypes.SELECT, raw: true, plain: true, bind: [id],
}); type: Sequelize.QueryTypes.SELECT,
raw: true,
plain: true,
},
);
return userq.totalPixels; return userq.totalPixels;
} catch (err) { } catch (err) {
return 0; return 0;
@ -95,7 +116,9 @@ class User {
async updateLogInTimestamp(): Promise<boolean> { async updateLogInTimestamp(): Promise<boolean> {
if (!this.regUser) return false; if (!this.regUser) return false;
try { try {
await this.regUser.update({ lastLogIn: Sequelize.literal('CURRENT_TIMESTAMP') }); await this.regUser.update({
lastLogIn: Sequelize.literal('CURRENT_TIMESTAMP'),
});
} catch (err) { } catch (err) {
return false; return false;
} }

View File

@ -20,6 +20,7 @@ import logger from '../core/logger';
import { Blacklist, Whitelist } from '../data/models'; import { Blacklist, Whitelist } from '../data/models';
import { MINUTE } from '../core/constants'; import { MINUTE } from '../core/constants';
// eslint-disable-next-line import/no-unresolved
import canvases from './canvases.json'; import canvases from './canvases.json';
import { imageABGR2Canvas } from '../core/Image'; import { imageABGR2Canvas } from '../core/Image';

View File

@ -9,6 +9,7 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
// eslint-disable-next-line import/no-unresolved
import canvases from './canvases.json'; import canvases from './canvases.json';
import webSockets from '../../socket/websockets'; import webSockets from '../../socket/websockets';

View File

@ -18,6 +18,7 @@ import Minecraft from '../core/minecraft';
import { drawUnsafe, setPixel } from '../core/draw'; import { drawUnsafe, setPixel } from '../core/draw';
import logger from '../core/logger'; import logger from '../core/logger';
import { APISOCKET_KEY } from '../core/config'; import { APISOCKET_KEY } from '../core/config';
import chatProvider from '../core/ChatProvider';
function heartbeat() { function heartbeat() {
this.isAlive = true; this.isAlive = true;
@ -182,7 +183,7 @@ class APISocketServer extends WebSocketEvents {
if (clr < 0 || clr > 32) return; if (clr < 0 || clr > 32) return;
// be aware that user null has no cd // be aware that user null has no cd
if (!minecraftid && !ip) { if (!minecraftid && !ip) {
setPixel("0", clr, x, y); setPixel('0', clr, x, y);
ws.send(JSON.stringify(['retpxl', null, null, true, 0, 0])); ws.send(JSON.stringify(['retpxl', null, null, true, 0, 0]));
return; return;
} }
@ -190,7 +191,7 @@ class APISocketServer extends WebSocketEvents {
user.ip = ip; user.ip = ip;
const { const {
error, success, waitSeconds, coolDownSeconds, error, success, waitSeconds, coolDownSeconds,
} = await drawUnsafe(user, "0", clr, x, y, null); } = await drawUnsafe(user, '0', clr, x, y, null);
ws.send(JSON.stringify([ ws.send(JSON.stringify([
'retpxl', 'retpxl',
(minecraftid) || ip, (minecraftid) || ip,
@ -237,13 +238,13 @@ class APISocketServer extends WebSocketEvents {
const chatname = (user.id) const chatname = (user.id)
? `[MC] ${user.regUser.name}` ? `[MC] ${user.regUser.name}`
: `[MC] ${minecraftname}`; : `[MC] ${minecraftname}`;
webSockets.broadcastChatMessage(chatname, msg, false); chatProvider.broadcastChatMessage(chatname, msg, false);
this.broadcastChatMessage(chatname, msg, true, ws); this.broadcastChatMessage(chatname, msg, true, ws);
return; return;
} }
if (command == 'chat') { if (command == 'chat') {
const [name, msg] = packet; const [name, msg] = packet;
webSockets.broadcastChatMessage(name, msg, false); chatProvider.broadcastChatMessage(name, msg, false);
this.broadcastChatMessage(name, msg, true, ws); this.broadcastChatMessage(name, msg, true, ws);
return; return;
} }

View File

@ -1,26 +0,0 @@
/*
* save the chat history
* TODO:
* This should really be saved in redis
*
* @flow
*/
class ChatHistory {
OP_CODE = 0xA5;
history: Array;
constructor() {
this.history = [];
}
addMessage(name, message) {
if (this.history.length > 20) {
this.history.shift();
}
this.history.push([name, message]);
}
}
const chatHistory = new ChatHistory();
export default chatHistory;

View File

@ -17,7 +17,7 @@ import RequestChatHistory from './packets/RequestChatHistory';
import CoolDownPacket from './packets/CoolDownPacket'; import CoolDownPacket from './packets/CoolDownPacket';
import ChangedMe from './packets/ChangedMe'; import ChangedMe from './packets/ChangedMe';
import ChatHistory from './ChatHistory'; import chatProvider from '../core/ChatProvider';
import authenticateClient from './verifyClient'; import authenticateClient from './verifyClient';
import WebSocketEvents from './WebSocketEvents'; import WebSocketEvents from './WebSocketEvents';
import webSockets from './websockets'; import webSockets from './websockets';
@ -217,17 +217,17 @@ class SocketServer extends WebSocketEvents {
webSockets.broadcastOnlineCounter(online); webSockets.broadcastOnlineCounter(online);
} }
static onTextMessage(message, ws) { static async onTextMessage(message, ws) {
if (ws.name && message) { if (ws.name && message) {
const waitLeft = ws.rateLimiter.tick(); const waitLeft = ws.rateLimiter.tick();
if (waitLeft) { if (waitLeft) {
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
ws.send(JSON.stringify(['info', `You are sending messages too fast, you have to wait ${Math.floor(waitLeft / 1000)}s :(`])); ws.send(JSON.stringify(['info', `You are sending messages too fast, you have to wait ${Math.floor(waitLeft / 1000)}s :(`]));
} else if (message.length > 300) { return;
// eslint-disable-next-line max-len }
ws.send(JSON.stringify(['info', 'You can\'t send a message this long :('])); const errorMsg = await chatProvider.sendMessage(ws.user, message);
} else { if (errorMsg) {
webSockets.broadcastChatMessage(ws.name, message); ws.send(JSON.stringify(['info', errorMsg]));
} }
} else { } else {
logger.info('Got empty message or message from unidentified ws'); logger.info('Got empty message or message from unidentified ws');
@ -278,7 +278,7 @@ class SocketServer extends WebSocketEvents {
break; break;
} }
case RequestChatHistory.OP_CODE: { case RequestChatHistory.OP_CODE: {
const history = JSON.stringify(ChatHistory.history); const history = JSON.stringify(chatProvider.history);
ws.send(history); ws.send(history);
break; break;
} }

View File

@ -6,7 +6,6 @@
*/ */
import logger from '../core/logger'; import logger from '../core/logger';
import ChatHistory from './ChatHistory';
import OnlineCounter from './packets/OnlineCounter'; import OnlineCounter from './packets/OnlineCounter';
import PixelUpdate from './packets/PixelUpdate'; import PixelUpdate from './packets/PixelUpdate';
@ -67,7 +66,6 @@ class WebSockets {
sendapi: boolean = true, sendapi: boolean = true,
) { ) {
logger.info(`Received chat message ${message} from ${name}`); logger.info(`Received chat message ${message} from ${name}`);
ChatHistory.addMessage(name, message);
this.listeners.forEach( this.listeners.forEach(
(listener) => listener.broadcastChatMessage(name, message, sendapi), (listener) => listener.broadcastChatMessage(name, message, sendapi),
); );

View File

@ -7,6 +7,7 @@
import isoFetch from 'isomorphic-fetch'; import isoFetch from 'isomorphic-fetch';
import HttpProxyAgent from 'http-proxy-agent'; import HttpProxyAgent from 'http-proxy-agent';
// eslint-disable-next-line import/no-unresolved
import proxylist from './proxies.json'; import proxylist from './proxies.json';
import logger from '../core/logger'; import logger from '../core/logger';