From 5cc626b8a05f28e2472768c789df7089914ff17b Mon Sep 17 00:00:00 2001 From: HF Date: Mon, 27 Apr 2020 18:24:57 +0200 Subject: [PATCH] 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) --- src/backup.js | 1 + src/components/HelpModal.jsx | 2 +- src/components/UserMessages.jsx | 2 +- src/core/ChatProvider.js | 114 ++++++++++++++++++++++++++++++++ src/core/Image.js | 1 + src/core/draw.js | 1 + src/core/me.js | 1 + src/core/passport.js | 1 + src/core/tileserver.js | 1 + src/data/models/RedisCanvas.js | 1 + src/data/models/User.js | 37 +++++++++-- src/routes/admintools.js | 1 + src/routes/api/mctp.js | 1 + src/socket/APISocketServer.js | 9 +-- src/socket/ChatHistory.js | 26 -------- src/socket/SocketServer.js | 16 ++--- src/socket/websockets.js | 2 - src/utils/proxiedFetch.js | 1 + 18 files changed, 169 insertions(+), 49 deletions(-) create mode 100644 src/core/ChatProvider.js delete mode 100644 src/socket/ChatHistory.js diff --git a/src/backup.js b/src/backup.js index 80c7f26..b37f7f1 100644 --- a/src/backup.js +++ b/src/backup.js @@ -24,6 +24,7 @@ import { createPngBackup, incrementialBackupRedis, } from './core/tilesBackup'; +// eslint-disable-next-line import/no-unresolved import canvases from './canvases.json'; /* diff --git a/src/components/HelpModal.jsx b/src/components/HelpModal.jsx index 44c8693..8f409f6 100644 --- a/src/components/HelpModal.jsx +++ b/src/components/HelpModal.jsx @@ -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 pixels and 7s on already set pixels. Higher zoomlevels take some time to update, the 3D globe gets updated at least once per day. - Have fun!

+ Have fun!

Discord: pixelplanet.fun/discord

Source on github

Reddit: r/PixelPlanetFun

diff --git a/src/components/UserMessages.jsx b/src/components/UserMessages.jsx index 4e272a3..a4a5bbb 100644 --- a/src/components/UserMessages.jsx +++ b/src/components/UserMessages.jsx @@ -79,7 +79,7 @@ class UserMessages extends React.Component { {(messages.includes('not_verified') && messages.splice(messages.indexOf('not_verified'), 1)) ? (

- 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} : Click here to request a new verification mail.} diff --git a/src/core/ChatProvider.js b/src/core/ChatProvider.js new file mode 100644 index 0000000..d91c381 --- /dev/null +++ b/src/core/ChatProvider.js @@ -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; diff --git a/src/core/Image.js b/src/core/Image.js index e9c72cd..3eed446 100644 --- a/src/core/Image.js +++ b/src/core/Image.js @@ -8,6 +8,7 @@ import RedisCanvas from '../data/models/RedisCanvas'; import logger from './logger'; import { getChunkOfPixel } from './utils'; import { TILE_SIZE } from './constants'; +// eslint-disable-next-line import/no-unresolved import canvases from './canvases.json'; import Palette from './Palette'; diff --git a/src/core/draw.js b/src/core/draw.js index f2ea784..8626f65 100644 --- a/src/core/draw.js +++ b/src/core/draw.js @@ -8,6 +8,7 @@ import { getChunkOfPixel, getOffsetOfPixel } from './utils'; import webSockets from '../socket/websockets'; import logger from './logger'; import RedisCanvas from '../data/models/RedisCanvas'; +// eslint-disable-next-line import/no-unresolved import canvases from './canvases.json'; import { THREE_CANVAS_HEIGHT } from './constants'; diff --git a/src/core/me.js b/src/core/me.js index 519c0c1..94a6717 100644 --- a/src/core/me.js +++ b/src/core/me.js @@ -5,6 +5,7 @@ * * @flow */ +// eslint-disable-next-line import/no-unresolved import canvases from './canvases.json'; diff --git a/src/core/passport.js b/src/core/passport.js index 633404b..18a6f98 100644 --- a/src/core/passport.js +++ b/src/core/passport.js @@ -33,6 +33,7 @@ passport.deserializeUser((req, id, done) => { RegUser.findOne({ where: { id } }).then((reguser) => { if (reguser) { user.regUser = reguser; + user.id = id; } else { user.id = null; } diff --git a/src/core/tileserver.js b/src/core/tileserver.js index 3f160ba..3a1ddfa 100644 --- a/src/core/tileserver.js +++ b/src/core/tileserver.js @@ -8,6 +8,7 @@ import fs from 'fs'; import type { Cell } from './Cell'; import logger from './logger'; +// eslint-disable-next-line import/no-unresolved import canvases from './canvases.json'; import Palette from './Palette'; import RedisCanvas from '../data/models/RedisCanvas'; diff --git a/src/data/models/RedisCanvas.js b/src/data/models/RedisCanvas.js index ef5fed8..f9fc591 100644 --- a/src/data/models/RedisCanvas.js +++ b/src/data/models/RedisCanvas.js @@ -6,6 +6,7 @@ import { THREE_TILE_SIZE, THREE_CANVAS_HEIGHT, } from '../../core/constants'; +// eslint-disable-next-line import/no-unresolved import canvases from './canvases.json'; import logger from '../../core/logger'; diff --git a/src/data/models/User.js b/src/data/models/User.js index d06636b..2954c11 100644 --- a/src/data/models/User.js +++ b/src/data/models/User.js @@ -9,7 +9,6 @@ import Sequelize from 'sequelize'; import redis from '../redis'; -import { randomDice } from '../../utils/random'; import logger from '../../core/logger'; import Model from '../sequelize'; @@ -32,8 +31,23 @@ class User { 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 { - if (coolDown == 0) return false; + if (!coolDown) return false; this.wait = Date.now() + coolDown; // PX is milliseconds expire await redis.setAsync(`cd:${canvasId}:ip:${this.ip}`, '', 'PX', coolDown); @@ -46,7 +60,9 @@ class User { async getWait(canvasId: number): Promise { let ttl: number = await redis.pttlAsync(`cd:${canvasId}:ip:${this.ip}`); 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); } logger.debug('ererer', ttl, typeof ttl); @@ -82,10 +98,15 @@ class User { return this.regUser.totalPixels; } 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; } catch (err) { return 0; @@ -95,7 +116,9 @@ class User { async updateLogInTimestamp(): Promise { if (!this.regUser) return false; try { - await this.regUser.update({ lastLogIn: Sequelize.literal('CURRENT_TIMESTAMP') }); + await this.regUser.update({ + lastLogIn: Sequelize.literal('CURRENT_TIMESTAMP'), + }); } catch (err) { return false; } diff --git a/src/routes/admintools.js b/src/routes/admintools.js index cee4087..621378d 100644 --- a/src/routes/admintools.js +++ b/src/routes/admintools.js @@ -20,6 +20,7 @@ import logger from '../core/logger'; import { Blacklist, Whitelist } from '../data/models'; import { MINUTE } from '../core/constants'; +// eslint-disable-next-line import/no-unresolved import canvases from './canvases.json'; import { imageABGR2Canvas } from '../core/Image'; diff --git a/src/routes/api/mctp.js b/src/routes/api/mctp.js index c2b0709..6347134 100644 --- a/src/routes/api/mctp.js +++ b/src/routes/api/mctp.js @@ -9,6 +9,7 @@ import type { Request, Response } from 'express'; +// eslint-disable-next-line import/no-unresolved import canvases from './canvases.json'; import webSockets from '../../socket/websockets'; diff --git a/src/socket/APISocketServer.js b/src/socket/APISocketServer.js index fb956d3..c328fd7 100644 --- a/src/socket/APISocketServer.js +++ b/src/socket/APISocketServer.js @@ -18,6 +18,7 @@ import Minecraft from '../core/minecraft'; import { drawUnsafe, setPixel } from '../core/draw'; import logger from '../core/logger'; import { APISOCKET_KEY } from '../core/config'; +import chatProvider from '../core/ChatProvider'; function heartbeat() { this.isAlive = true; @@ -182,7 +183,7 @@ class APISocketServer extends WebSocketEvents { if (clr < 0 || clr > 32) return; // be aware that user null has no cd if (!minecraftid && !ip) { - setPixel("0", clr, x, y); + setPixel('0', clr, x, y); ws.send(JSON.stringify(['retpxl', null, null, true, 0, 0])); return; } @@ -190,7 +191,7 @@ class APISocketServer extends WebSocketEvents { user.ip = ip; const { error, success, waitSeconds, coolDownSeconds, - } = await drawUnsafe(user, "0", clr, x, y, null); + } = await drawUnsafe(user, '0', clr, x, y, null); ws.send(JSON.stringify([ 'retpxl', (minecraftid) || ip, @@ -237,13 +238,13 @@ class APISocketServer extends WebSocketEvents { const chatname = (user.id) ? `[MC] ${user.regUser.name}` : `[MC] ${minecraftname}`; - webSockets.broadcastChatMessage(chatname, msg, false); + chatProvider.broadcastChatMessage(chatname, msg, false); this.broadcastChatMessage(chatname, msg, true, ws); return; } if (command == 'chat') { const [name, msg] = packet; - webSockets.broadcastChatMessage(name, msg, false); + chatProvider.broadcastChatMessage(name, msg, false); this.broadcastChatMessage(name, msg, true, ws); return; } diff --git a/src/socket/ChatHistory.js b/src/socket/ChatHistory.js deleted file mode 100644 index 4156859..0000000 --- a/src/socket/ChatHistory.js +++ /dev/null @@ -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; diff --git a/src/socket/SocketServer.js b/src/socket/SocketServer.js index 42d6440..228fe2c 100644 --- a/src/socket/SocketServer.js +++ b/src/socket/SocketServer.js @@ -17,7 +17,7 @@ import RequestChatHistory from './packets/RequestChatHistory'; import CoolDownPacket from './packets/CoolDownPacket'; import ChangedMe from './packets/ChangedMe'; -import ChatHistory from './ChatHistory'; +import chatProvider from '../core/ChatProvider'; import authenticateClient from './verifyClient'; import WebSocketEvents from './WebSocketEvents'; import webSockets from './websockets'; @@ -217,17 +217,17 @@ class SocketServer extends WebSocketEvents { webSockets.broadcastOnlineCounter(online); } - static onTextMessage(message, ws) { + static async onTextMessage(message, ws) { if (ws.name && message) { const waitLeft = ws.rateLimiter.tick(); if (waitLeft) { // 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 :(`])); - } else if (message.length > 300) { - // eslint-disable-next-line max-len - ws.send(JSON.stringify(['info', 'You can\'t send a message this long :('])); - } else { - webSockets.broadcastChatMessage(ws.name, message); + return; + } + const errorMsg = await chatProvider.sendMessage(ws.user, message); + if (errorMsg) { + ws.send(JSON.stringify(['info', errorMsg])); } } else { logger.info('Got empty message or message from unidentified ws'); @@ -278,7 +278,7 @@ class SocketServer extends WebSocketEvents { break; } case RequestChatHistory.OP_CODE: { - const history = JSON.stringify(ChatHistory.history); + const history = JSON.stringify(chatProvider.history); ws.send(history); break; } diff --git a/src/socket/websockets.js b/src/socket/websockets.js index 1dba69a..3ed27d8 100644 --- a/src/socket/websockets.js +++ b/src/socket/websockets.js @@ -6,7 +6,6 @@ */ import logger from '../core/logger'; -import ChatHistory from './ChatHistory'; import OnlineCounter from './packets/OnlineCounter'; import PixelUpdate from './packets/PixelUpdate'; @@ -67,7 +66,6 @@ class WebSockets { sendapi: boolean = true, ) { logger.info(`Received chat message ${message} from ${name}`); - ChatHistory.addMessage(name, message); this.listeners.forEach( (listener) => listener.broadcastChatMessage(name, message, sendapi), ); diff --git a/src/utils/proxiedFetch.js b/src/utils/proxiedFetch.js index 1110bdb..5764479 100644 --- a/src/utils/proxiedFetch.js +++ b/src/utils/proxiedFetch.js @@ -7,6 +7,7 @@ import isoFetch from 'isomorphic-fetch'; import HttpProxyAgent from 'http-proxy-agent'; +// eslint-disable-next-line import/no-unresolved import proxylist from './proxies.json'; import logger from '../core/logger';