Merge branch 'master' into production

This commit is contained in:
HF 2020-04-29 19:34:00 +02:00
commit 50e3a95161
15 changed files with 224 additions and 82 deletions

View File

@ -189,12 +189,14 @@ export function receiveChatMessage(
name: string,
text: string,
country: string,
channel: number,
): Action {
return {
type: 'RECEIVE_CHAT_MESSAGE',
name,
text,
country,
channel,
};
}
@ -620,6 +622,13 @@ export function showChatModal(): Action {
return showModal('CHAT');
}
export function setChatChannel(channelId: number): Action {
return {
type: 'SET_CHAT_CHANNEL',
channelId,
};
}
export function hideModal(): Action {
return {
type: 'HIDE_MODAL',

View File

@ -56,8 +56,11 @@ export type Action =
| { type: 'RECEIVE_CHAT_MESSAGE',
name: string,
text: string,
country: string }
country: string,
channel: number,
}
| { type: 'RECEIVE_CHAT_HISTORY', data: Array }
| { type: 'SET_CHAT_CHANNEL', channelId: number }
| { type: 'RECEIVE_ME',
name: string,
waitSeconds: number,

View File

@ -43,8 +43,8 @@ function init() {
ProtocolClient.on('onlineCounter', ({ online }) => {
store.dispatch(receiveOnline(online));
});
ProtocolClient.on('chatMessage', (name, text, country) => {
store.dispatch(receiveChatMessage(name, text, country));
ProtocolClient.on('chatMessage', (name, text, country, channelId) => {
store.dispatch(receiveChatMessage(name, text, country, channelId));
});
ProtocolClient.on('chatHistory', (data) => {
store.dispatch(receiveChatHistory(data));

View File

@ -12,27 +12,23 @@ import ChatInput from './ChatInput';
import { colorFromText, splitCoordsInString } from '../core/utils';
function onError() {
this.onerror = null;
this.src = './cf/xx.gif';
}
const Chat = ({ chatMessages }) => {
const Chat = ({ chatMessages, chatChannel }) => {
const listRef = useRef();
const { stayScrolled } = useStayScrolled(listRef, {
initialScroll: Infinity,
});
const channelMessages = chatMessages[chatChannel];
useLayoutEffect(() => {
stayScrolled();
}, [chatMessages.length]);
}, [channelMessages.length]);
return (
<div style={{ height: '100%' }}>
<ul className="chatarea" ref={listRef}>
{
chatMessages.map((message) => (
channelMessages.map((message) => (
<p className="chatmsg">
{(message[0] === 'info')
? <span style={{ color: '#cc0000' }}>{message[1]}</span>
@ -42,7 +38,10 @@ const Chat = ({ chatMessages }) => {
alt=""
title={`${message[2]}`}
src={`${window.assetserver}/cf/${message[2]}.gif`}
onError={onError}
onError={(e) => {
e.target.onerror = null;
e.target.src = './cf/xx.gif';
}}
/>
&nbsp;
<span
@ -75,7 +74,8 @@ const Chat = ({ chatMessages }) => {
function mapStateToProps(state: State) {
const { chatMessages } = state.user;
return { chatMessages };
const { chatChannel } = state.gui;
return { chatMessages, chatChannel };
}
export default connect(mapStateToProps)(Chat);

View File

@ -10,7 +10,8 @@ import { connect } from 'react-redux';
import type { State } from '../reducers';
import ProtocolClient from '../socket/ProtocolClient';
import { showUserAreaModal } from '../actions';
import { showUserAreaModal, setChatChannel } from '../actions';
import { CHAT_CHANNELS } from '../core/constants';
class ChatInput extends React.Component {
constructor() {
@ -22,44 +23,80 @@ class ChatInput extends React.Component {
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e) {
handleSubmit(e, channelId) {
e.preventDefault();
const { message } = this.state;
if (!message) return;
// send message via websocket
ProtocolClient.sendMessage(message);
ProtocolClient.sendChatMessage(message, channelId);
this.setState({
message: '',
});
}
render() {
if (this.props.name) {
const {
name, chatChannel, open, setChannel,
} = this.props;
const {
message,
} = this.state;
const selectedChannel = CHAT_CHANNELS[chatChannel];
if (name) {
return (
<div className="chatinput">
<form onSubmit={this.handleSubmit}>
<form
onSubmit={(e) => { this.handleSubmit(e, chatChannel); }}
style={{ display: 'inline' }}
>
<input
style={{ maxWidth: '80%', width: '240px' }}
value={this.state.message}
style={{ maxWidth: '58%', width: '240px' }}
value={message}
onChange={(evt) => this.setState({ message: evt.target.value })}
type="text"
placeholder="Chat here"
/>
<button id="chatmsginput" type="submit">Send</button>
<button
id="chatmsginput"
type="submit"
style={{ height: 22 }}
>
</button>
</form>
<select
onChange={(evt) => setChannel(evt.target.selectedIndex)}
style={{ height: 22 }}
>
{
CHAT_CHANNELS.map((ch) => (
<option selected={ch === selectedChannel}>{ch}</option>
))
}
</select>
</div>
);
}
return (
<div className="modallink" onClick={this.props.open} style={{ textAlign: 'center', fontSize: 13 }}>You must be logged in to chat</div>
<div
className="modallink"
onClick={open}
style={{ textAlign: 'center', fontSize: 13 }}
role="button"
tabIndex={0}
>
You must be logged in to chat
</div>
);
}
}
function mapStateToProps(state: State) {
const { name } = state.user;
return { name };
const { chatChannel } = state.gui;
return { name, chatChannel };
}
function mapDispatchToProps(dispatch) {
@ -67,6 +104,9 @@ function mapDispatchToProps(dispatch) {
open() {
dispatch(showUserAreaModal());
},
setChannel(channelId) {
dispatch(setChatChannel(channelId));
},
};
}

View File

@ -6,6 +6,8 @@ import redis from '../data/redis';
import User from '../data/models/User';
import webSockets from '../socket/websockets';
import { CHAT_CHANNELS } from './constants';
class ChatProvider {
/*
@ -16,20 +18,23 @@ class ChatProvider {
constructor() {
this.history = [];
for (let i = 0; i < CHAT_CHANNELS.length; i += 1) {
this.history.push([]);
}
this.caseCheck = /^[A-Z !.]*$/;
this.filters = [
{
regexp: /ADMIN/gi,
matches: 2,
},
{
regexp: /FUCK/gi,
matches: 2,
matches: 3,
},
{
regexp: /JEBAĆ/gi,
matches: 2,
},
{
regexp: /FUCK/gi,
matches: 3,
},
{
regexp: /FACK/gi,
matches: 3,
@ -44,20 +49,21 @@ class ChatProvider {
this.mutedCountries = [];
}
addMessage(name, message, country) {
if (this.history.length > 20) {
this.history.shift();
addMessage(name, message, country, channelId = 0) {
const channelHistory = this.history[channelId];
if (channelHistory.length > 20) {
channelHistory.shift();
}
this.history.push([name, message, country]);
channelHistory.push([name, message, country]);
}
async sendMessage(user, message) {
async sendMessage(user, message, channelId: number = 0) {
const name = (user.regUser) ? user.regUser.name : null;
const country = (name.endsWith('berg') || name.endsWith('stein'))
? 'il'
: (user.country || 'xx');
if (name.startsWith('popie')) return null;
if (name.startsWith('popi')) return null;
if (!name) {
// eslint-disable-next-line max-len
return 'Couldn\'t send your message, pls log out and back in again.';
@ -77,7 +83,7 @@ class ChatProvider {
const filter = this.filters[i];
const count = (message.match(filter.regexp) || []).length;
if (count >= filter.matches) {
ChatProvider.mute(name, 30);
ChatProvider.mute(name, channelId, 30);
return 'Ow no! Spam protection decided to mute you';
}
}
@ -104,17 +110,22 @@ class ChatProvider {
if (cmd === 'mute') {
const timeMin = Number(args.slice(-1));
if (Number.isNaN(timeMin)) {
return ChatProvider.mute(args.join(' '));
return ChatProvider.mute(args.join(' '), channelId);
}
return ChatProvider.mute(args.slice(0, -1).join(' '), timeMin);
return ChatProvider.mute(
args.slice(0, -1).join(' '),
channelId,
timeMin,
);
} if (cmd === 'unmute') {
return ChatProvider.unmute(args.join(' '));
return ChatProvider.unmute(args.join(' '), channelId);
} if (cmd === 'mutec' && args[0]) {
const cc = args[0].toLowerCase();
this.mutedCountries.push(cc);
webSockets.broadcastChatMessage(
'info',
`Country ${cc} has been muted`,
channelId,
);
return null;
} if (cmd === 'unmutec' && args[0]) {
@ -126,6 +137,7 @@ class ChatProvider {
webSockets.broadcastChatMessage(
'info',
`Country ${cc} has been unmuted`,
channelId,
);
return null;
}
@ -146,8 +158,8 @@ class ChatProvider {
}
return `You are muted for another ${muted} seconds`;
}
this.addMessage(name, message, country);
webSockets.broadcastChatMessage(name, message, country);
this.addMessage(name, message, country, channelId);
webSockets.broadcastChatMessage(name, message, country, channelId);
return null;
}
@ -155,10 +167,11 @@ class ChatProvider {
name,
message,
country: string = 'xx',
channelId: number = 0,
sendapi: boolean = true,
) {
this.addMessage(name, message, country);
webSockets.broadcastChatMessage(name, message, country, sendapi);
this.addMessage(name, message, country, channelId);
webSockets.broadcastChatMessage(name, message, country, channelId, sendapi);
}
/*
@ -166,8 +179,8 @@ class ChatProvider {
* singleton
*/
// eslint-disable-next-line class-methods-use-this
automute(name) {
ChatProvider.mute(name, 600);
automute(name, channelId = 0) {
ChatProvider.mute(name, channelId, 600);
}
static async checkIfMuted(user) {
@ -176,7 +189,7 @@ class ChatProvider {
return ttl;
}
static async mute(name, timeMin = null) {
static async mute(name, channelId = 0, timeMin = null) {
const id = await User.name2Id(name);
if (!id) {
return `Couldn't find user ${name}`;
@ -186,19 +199,25 @@ class ChatProvider {
const ttl = timeMin * 60;
await redis.setAsync(key, '', 'EX', ttl);
if (timeMin !== 600) {
webSockets.broadcastChatMessage('info',
`${name} has been muted for ${timeMin}min`);
webSockets.broadcastChatMessage(
'info',
`${name} has been muted for ${timeMin}min`,
channelId,
);
}
} else {
await redis.setAsync(key, '');
webSockets.broadcastChatMessage('info',
`${name} has been muted forever`);
webSockets.broadcastChatMessage(
'info',
`${name} has been muted forever`,
channelId,
);
}
logger.info(`Muted user ${id}`);
return null;
}
static async unmute(name) {
static async unmute(name, channelId = 0) {
const id = await User.name2Id(name);
if (!id) {
return `Couldn't find user ${name}`;
@ -208,8 +227,11 @@ class ChatProvider {
if (delKeys !== 1) {
return `User ${name} is not muted`;
}
webSockets.broadcastChatMessage('info',
`${name} has been unmuted`);
webSockets.broadcastChatMessage(
'info',
`${name} has been unmuted`,
channelId,
);
logger.info(`Unmuted user ${id}`);
return null;
}

View File

@ -88,3 +88,6 @@ export const MINUTE = 60 * SECOND;
export const HOUR = 60 * MINUTE;
export const DAY = 24 * HOUR;
export const MONTH = 30 * DAY;
// available Chat Channels
export const CHAT_CHANNELS = ['en', 'int'];

View File

@ -17,6 +17,7 @@ export type GUIState = {
compactPalette: boolean,
paletteOpen: boolean,
menuOpen: boolean,
chatChannel: number,
};
const initialState: GUIState = {
@ -31,6 +32,7 @@ const initialState: GUIState = {
compactPalette: false,
paletteOpen: true,
menuOpen: false,
chatChannel: 0,
};
@ -95,6 +97,13 @@ export default function gui(
};
}
case 'SET_CHAT_CHANNEL': {
return {
...state,
chatChannel: action.channelId,
};
}
case 'SELECT_COLOR': {
const {
compactPalette,

View File

@ -44,7 +44,10 @@ const initialState: UserState = {
mailreg: false,
totalRanking: {},
totalDailyRanking: {},
chatMessages: [['info', 'Welcome to the PixelPlanet Chat', 'il']],
chatMessages: [
[['info', 'Welcome to the PixelPlanet Chat', 'il']],
[['info', 'Welcome to the PixelPlanet Chat', 'il']],
],
minecraftname: null,
isOnMobile: false,
notification: null,
@ -119,14 +122,21 @@ export default function user(
}
case 'RECEIVE_CHAT_MESSAGE': {
const { name, text, country } = action;
let { chatMessages } = state;
if (chatMessages.length > 50) {
chatMessages = chatMessages.slice(-50);
const {
name, text, country, channel,
} = action;
const chatMessages = state.chatMessages.slice();
let channelMessages = chatMessages[channel];
if (channelMessages.length > 50) {
channelMessages = channelMessages.slice(-50);
}
channelMessages = channelMessages.concat([
[name, text, country]
]);
chatMessages[channel] = channelMessages;
return {
...state,
chatMessages: chatMessages.concat([[name, text, country]]),
chatMessages,
};
}

View File

@ -30,7 +30,7 @@ async function verifyClient(info, done) {
const ip = await getIPFromRequest(req);
if (!headers.authorization
|| headers.authorization != `Bearer ${APISOCKET_KEY}`) {
|| headers.authorization !== `Bearer ${APISOCKET_KEY}`) {
logger.warn(`API ws request from ${ip} authenticated`);
return done(false);
}
@ -82,8 +82,15 @@ class APISocketServer extends WebSocketEvents {
setInterval(this.ping, 45 * 1000);
}
broadcastChatMessage(name, msg, country, sendapi, ws = null) {
if (!sendapi) return;
broadcastChatMessage(
name,
msg,
country,
channelId,
sendapi,
ws = null,
) {
if (!sendapi || channelId !== 0) return;
const sendmsg = JSON.stringify(['msg', name, msg]);
this.wss.clients.forEach((client) => {
@ -238,14 +245,14 @@ class APISocketServer extends WebSocketEvents {
const chatname = (user.id)
? `[MC] ${user.regUser.name}`
: `[MC] ${minecraftname}`;
chatProvider.broadcastChatMessage(chatname, msg, 'xx', false);
this.broadcastChatMessage(chatname, msg, 'xx', true, ws);
chatProvider.broadcastChatMessage(chatname, msg, 'xx', 0, false);
this.broadcastChatMessage(chatname, msg, 'xx', 0, true, ws);
return;
}
if (command == 'chat') {
const [name, msg] = packet;
chatProvider.broadcastChatMessage(name, msg, 'xx', false);
this.broadcastChatMessage(name, msg, 'xx', true, ws);
chatProvider.broadcastChatMessage(name, msg, 'xx', 0, false);
this.broadcastChatMessage(name, msg, 'xx', 0, true, ws);
return;
}
if (command == 'linkacc') {

View File

@ -132,8 +132,10 @@ class ProtocolClient extends EventEmitter {
if (this.isConnected) this.ws.send(buffer);
}
sendMessage(message) {
if (this.isConnected) this.ws.send(message);
sendChatMessage(message, channelId) {
if (this.isConnected) {
this.ws.send(JSON.stringify([message, channelId]));
}
}
onMessage({ data: message }) {
@ -160,10 +162,10 @@ class ProtocolClient extends EventEmitter {
this.emit('chatHistory', data);
return;
}
if (data.length === 3) {
if (data.length === 4) {
// Ordinary array: Chat message
const [name, text, country] = data;
this.emit('chatMessage', name, text, country);
const [name, text, country, channelId] = data;
this.emit('chatMessage', name, text, country, channelId);
}
} else {
// string = name

View File

@ -145,8 +145,13 @@ class SocketServer extends WebSocketEvents {
this.broadcast(buffer);
}
broadcastChatMessage(name: string, message: string, country: string) {
const text = JSON.stringify([name, message, country]);
broadcastChatMessage(
name: string,
message: string,
country: string,
channelId: number = 0,
) {
const text = JSON.stringify([name, message, country, channelId]);
this.wss.clients.forEach((ws) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(text);
@ -217,23 +222,48 @@ class SocketServer extends WebSocketEvents {
webSockets.broadcastOnlineCounter(online);
}
static async onTextMessage(message, ws) {
static async onTextMessage(text, ws) {
let message;
let channelId;
try {
const data = JSON.parse(text);
[message, channelId] = data;
channelId = Number(channelId);
if (Number.isNaN(channelId)) {
throw new Error('NaN');
}
} catch {
logger.warn(
`Received unparseable message from ${ws.name} on websocket: ${text}`,
);
return;
}
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 :(`, 'il']));
ws.send(JSON.stringify([
'info',
// eslint-disable-next-line max-len
`You are sending messages too fast, you have to wait ${Math.floor(waitLeft / 1000)}s :(`,
'il',
channelId,
]));
return;
}
const errorMsg = await chatProvider.sendMessage(ws.user, message);
const errorMsg = await chatProvider.sendMessage(
ws.user,
message,
channelId,
);
if (errorMsg) {
ws.send(JSON.stringify(['info', errorMsg, 'il']));
ws.send(JSON.stringify(['info', errorMsg, 'il', channelId]));
}
if (ws.last_message && ws.last_message === message) {
ws.message_repeat += 1;
if (ws.message_repeat >= 3) {
logger.info(`User ${ws.name} got automuted`);
chatProvider.automute(ws.name);
chatProvider.automute(ws.name, channelId);
ws.message_repeat = 0;
}
} else {

View File

@ -14,7 +14,7 @@ class WebSocketEvents {
broadcastPixelBuffer(canvasId: number, chunkid: number, buffer: Buffer) {
}
broadcastChatMessage(name: string, message: string) {
broadcastChatMessage(name: string, message: string, channelId: number) {
}
broadcastMinecraftLink(name: string, minecraftid: string, accepted: boolean) {

View File

@ -5,7 +5,6 @@
*
*/
import logger from '../core/logger';
import OnlineCounter from './packets/OnlineCounter';
import PixelUpdate from './packets/PixelUpdate';
@ -64,14 +63,16 @@ class WebSockets {
name: string,
message: string,
country: string,
channelId: number = 0,
sendapi: boolean = true,
) {
country == country || 'xx';
country = country || 'xx';
this.listeners.forEach(
(listener) => listener.broadcastChatMessage(
name,
message,
country,
channelId,
sendapi,
),
);

View File

@ -173,6 +173,12 @@ export default (store) => (next) => (action) => {
case 'RECEIVE_CHAT_MESSAGE': {
if (!chatNotify) break;
const { chatChannel } = state.gui;
if (action.channel !== chatChannel) {
break;
}
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();