add websocket messages or chat joining and leaving

This commit is contained in:
HF 2020-11-27 23:48:59 +01:00
parent 8f24a34a1d
commit 46ba5188b5
19 changed files with 403 additions and 142 deletions

View File

@ -369,7 +369,6 @@ export function move([dx, dy]: Cell): ThunkAction {
} }
export function moveDirection([vx, vy]: Cell): ThunkAction { export function moveDirection([vx, vy]: Cell): ThunkAction {
// TODO check direction is unitary vector
return (dispatch, getState) => { return (dispatch, getState) => {
const { viewscale } = getState().canvas; const { viewscale } = getState().canvas;
@ -768,7 +767,7 @@ export function setChatChannel(cid: number): Action {
}; };
} }
export function addChatChannel(channel: Array): Action { export function addChatChannel(channel: Object): Action {
return { return {
type: 'ADD_CHAT_CHANNEL', type: 'ADD_CHAT_CHANNEL',
channel, channel,

View File

@ -70,7 +70,7 @@ export type Action =
} }
| { type: 'RECEIVE_CHAT_HISTORY', cid: number, history: Array } | { type: 'RECEIVE_CHAT_HISTORY', cid: number, history: Array }
| { type: 'SET_CHAT_CHANNEL', cid: number } | { type: 'SET_CHAT_CHANNEL', cid: number }
| { type: 'ADD_CHAT_CHANNEL', channel: Array } | { type: 'ADD_CHAT_CHANNEL', channel: Object }
| { type: 'REMOVE_CHAT_CHANNEL', cid: number } | { type: 'REMOVE_CHAT_CHANNEL', cid: number }
| { type: 'SET_CHAT_FETCHING', fetching: boolean } | { type: 'SET_CHAT_FETCHING', fetching: boolean }
| { type: 'SET_CHAT_INPUT_MSG', message: string } | { type: 'SET_CHAT_INPUT_MSG', message: string }

View File

@ -17,6 +17,8 @@ import {
receiveCoolDown, receiveCoolDown,
receiveChatMessage, receiveChatMessage,
receivePixelReturn, receivePixelReturn,
addChatChannel,
removeChatChannel,
setMobile, setMobile,
tryPlacePixel, tryPlacePixel,
} from './actions'; } from './actions';
@ -31,7 +33,6 @@ import ProtocolClient from './socket/ProtocolClient';
function init() { function init() {
initRenderer(store, false); initRenderer(store, false);
let nameRegExp = null;
ProtocolClient.on('pixelUpdate', ({ ProtocolClient.on('pixelUpdate', ({
i, j, offset, color, i, j, offset, color,
}) => { }) => {
@ -49,9 +50,6 @@ function init() {
ProtocolClient.on('onlineCounter', ({ online }) => { ProtocolClient.on('onlineCounter', ({ online }) => {
store.dispatch(receiveOnline(online)); store.dispatch(receiveOnline(online));
}); });
ProtocolClient.on('setWsName', (name) => {
nameRegExp = new RegExp(`(^|\\s+)(@${name})(\\s+|$)`, 'g');
});
ProtocolClient.on('chatMessage', ( ProtocolClient.on('chatMessage', (
name, name,
text, text,
@ -59,6 +57,8 @@ function init() {
channelId, channelId,
userId, userId,
) => { ) => {
const state = store.getState();
const { nameRegExp } = state.user;
const isPing = (nameRegExp && text.match(nameRegExp)); const isPing = (nameRegExp && text.match(nameRegExp));
store.dispatch(receiveChatMessage( store.dispatch(receiveChatMessage(
name, name,
@ -72,6 +72,12 @@ function init() {
ProtocolClient.on('changedMe', () => { ProtocolClient.on('changedMe', () => {
store.dispatch(fetchMe()); store.dispatch(fetchMe());
}); });
ProtocolClient.on('remch', (cid) => {
store.dispatch(removeChatChannel(cid));
});
ProtocolClient.on('addch', (channel) => {
store.dispatch(addChatChannel(channel));
});
window.addEventListener('hashchange', () => { window.addEventListener('hashchange', () => {
store.dispatch(urlChange()); store.dispatch(urlChange());

View File

@ -3,8 +3,9 @@ import logger from './logger';
import redis from '../data/redis'; import redis from '../data/redis';
import User from '../data/models/User'; import User from '../data/models/User';
import webSockets from '../socket/websockets'; import webSockets from '../socket/websockets';
import { Channel, RegUser } from '../data/models'; import { Channel, RegUser, UserChannel } from '../data/models';
import ChatMessageBuffer from './ChatMessageBuffer'; import ChatMessageBuffer from './ChatMessageBuffer';
import { cheapDetector } from './isProxy';
import { CHAT_CHANNELS, EVENT_USER_NAME, INFO_USER_NAME } from './constants'; import { CHAT_CHANNELS, EVENT_USER_NAME, INFO_USER_NAME } from './constants';
@ -100,6 +101,34 @@ export class ChatProvider {
this.eventUserId = eventUser[0].id; this.eventUserId = eventUser[0].id;
} }
static async addUserToChannel(
userId,
channelId,
channelArray,
notify = true,
) {
/*
* since UserId and ChannelId are primary keys,
* this will throw if already exists
*/
const relation = await UserChannel.create({
UserId: userId,
ChannelId: channelId,
}, {
raw: true,
});
console.log('HEREEEEE HHEEERRREEE');
console.log(relation);
webSockets.broadcastAddChatChannel(
userId,
channelId,
channelArray,
notify,
);
}
userHasChannelAccess(user, cid, write = false) { userHasChannelAccess(user, cid, write = false) {
if (this.defaultChannels[cid]) { if (this.defaultChannels[cid]) {
if (!write || user.regUser) { if (!write || user.regUser) {
@ -111,18 +140,110 @@ export class ChatProvider {
return false; return false;
} }
checkIfDm(user, cid) {
if (this.defaultChannels[cid]) {
return null;
}
const channelArray = user.channels[cid];
if (channelArray && channelArray.length === 4) {
return user.channels[cid][4];
}
return null;
}
getHistory(cid, limit = 30) { getHistory(cid, limit = 30) {
return this.chatMessageBuffer.getMessages(cid, limit); return this.chatMessageBuffer.getMessages(cid, limit);
} }
adminCommands(message: string, channelId: number) {
// admin commands
const cmdArr = message.split(' ');
const cmd = cmdArr[0].substr(1);
const args = cmdArr.slice(1);
switch (cmd) {
case 'mute': {
const timeMin = Number(args.slice(-1));
if (Number.isNaN(timeMin)) {
return this.mute(args.join(' '), channelId);
}
return this.mute(
args.slice(0, -1).join(' '),
channelId,
timeMin,
);
}
case 'unmute':
return this.unmute(args.join(' '), channelId);
case 'mutec': {
if (args[0]) {
const cc = args[0].toLowerCase();
this.mutedCountries.push(cc);
this.broadcastChatMessage(
'info',
`Country ${cc} has been muted`,
channelId,
this.infoUserId,
);
return null;
}
return 'No country defined for mutec';
}
case 'unmutec': {
if (args[0]) {
const cc = args[0].toLowerCase();
if (!this.mutedCountries.includes(cc)) {
return `Country ${cc} is not muted`;
}
this.mutedCountries = this.mutedCountries.filter((c) => c !== cc);
this.broadcastChatMessage(
'info',
`Country ${cc} has been unmuted`,
channelId,
this.infoUserId,
);
return null;
}
if (this.mutedCountries.length) {
this.broadcastChatMessage(
'info',
`Countries ${this.mutedCountries} have been unmuted`,
channelId,
this.infoUserId,
);
this.mutedCountries = [];
return null;
}
return 'No country is currently muted';
}
default:
return `Couln't parse command ${cmd}`;
}
}
async sendMessage(user, message, channelId: number = 0) { async sendMessage(user, message, channelId: number = 0) {
const { id } = user; const { id } = user;
const name = user.getName(); const name = user.getName();
if (!user.isAdmin() && await cheapDetector(user.ip)) {
logger.info(
`${name} / ${user.ip} tried to send chat message with proxy`,
);
return 'You can not send chat messages with proxy';
}
if (!name || !id) { if (!name || !id) {
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
return 'Couldn\'t send your message, pls log out and back in again.'; return 'Couldn\'t send your message, pls log out and back in again.';
} }
if (user.isAdmin() && message.charAt(0) === '/') {
return this.adminCommands(message, channelId);
}
if (!this.userHasChannelAccess(user, channelId)) { if (!this.userHasChannelAccess(user, channelId)) {
return 'You don\'t have access to this channel'; return 'You don\'t have access to this channel';
} }
@ -181,49 +302,6 @@ export class ChatProvider {
return 'You can\'t send a message this long :('; return 'You can\'t send a message this long :(';
} }
if (user.isAdmin() && message.charAt(0) === '/') {
// admin commands
const cmdArr = message.split(' ');
const cmd = cmdArr[0].substr(1);
const args = cmdArr.slice(1);
if (cmd === 'mute') {
const timeMin = Number(args.slice(-1));
if (Number.isNaN(timeMin)) {
return this.mute(args.join(' '), channelId);
}
return this.mute(
args.slice(0, -1).join(' '),
channelId,
timeMin,
);
} if (cmd === 'unmute') {
return this.unmute(args.join(' '), channelId);
} if (cmd === 'mutec' && args[0]) {
const cc = args[0].toLowerCase();
this.mutedCountries.push(cc);
this.broadcastChatMessage(
'info',
`Country ${cc} has been muted`,
channelId,
this.infoUserId,
);
return null;
} if (cmd === 'unmutec' && args[0]) {
const cc = args[0].toLowerCase();
if (!this.mutedCountries.includes(cc)) {
return `Country ${cc} is not muted`;
}
this.mutedCountries = this.mutedCountries.filter((c) => c !== cc);
this.broadcastChatMessage(
'info',
`Country ${cc} has been unmuted`,
channelId,
this.infoUserId,
);
return null;
}
}
if (message.match(this.cyrillic) && channelId === this.enChannelId) { if (message.match(this.cyrillic) && channelId === this.enChannelId) {
return 'Please use int channel'; return 'Please use int channel';
} }
@ -232,6 +310,21 @@ export class ChatProvider {
return 'Your country is temporary muted from chat'; return 'Your country is temporary muted from chat';
} }
if (user.last_message && user.last_message === message) {
user.message_repeat += 1;
if (user.message_repeat >= 4) {
this.mute(name, channelId, 60);
user.message_repeat = 0;
return 'Stop flooding.';
}
} else {
user.message_repeat = 0;
user.last_message = message;
}
logger.info(
`Received chat message ${message} from ${name} / ${user.ip}`,
);
this.broadcastChatMessage( this.broadcastChatMessage(
name, name,
message, message,
@ -270,16 +363,6 @@ export class ChatProvider {
); );
} }
automute(name, channelId) {
this.mute(name, channelId, 60);
this.broadcastChatMessage(
'info',
`${name} has been muted for spam for 60min`,
channelId,
this.infoUserId,
);
}
static async checkIfMuted(user) { static async checkIfMuted(user) {
const key = `mute:${user.id}`; const key = `mute:${user.id}`;
const ttl: number = await redis.ttlAsync(key); const ttl: number = await redis.ttlAsync(key);
@ -296,14 +379,12 @@ export class ChatProvider {
if (timeMin) { if (timeMin) {
const ttl = timeMin * 60; const ttl = timeMin * 60;
await redis.setAsync(key, '', 'EX', ttl); await redis.setAsync(key, '', 'EX', ttl);
if (timeMin !== 600 && timeMin !== 60) { this.broadcastChatMessage(
this.broadcastChatMessage( 'info',
'info', `${name} has been muted for ${timeMin}min`,
`${name} has been muted for ${timeMin}min`, channelId,
channelId, this.infoUserId,
this.infoUserId, );
);
}
} else { } else {
await redis.setAsync(key, ''); await redis.setAsync(key, '');
this.broadcastChatMessage( this.broadcastChatMessage(

View File

@ -260,3 +260,13 @@ export function setBrightness(hex, dark: boolean = false) {
} }
return `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`; return `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`;
} }
/*
* create RegExp to search for ping in chat messages
* @param name name
* @return regular expression to search for name in message
*/
export function createNameRegExp(name: string) {
if (!name) return null;
return new RegExp(`(^|\\s+)(@${name})(\\s+|$)`, 'g');
}

View File

@ -64,17 +64,28 @@ class User {
dmu1, dmu1,
dmu2, dmu2,
} = reguser.channel[i]; } = reguser.channel[i];
// in DMs the name is the name of the other user
let { name } = reguser.channel[i];
if (type === 1) {
name = (dmu1.id === this.id) ? dmu2.name : dmu1.name;
}
this.channelIds.push(id); this.channelIds.push(id);
this.channels[id] = [ if (type === 1) {
name, /* in DMs:
type, * the name is the name of the other user
lastTs, * id also gets grabbed
]; */
const name = (dmu1.id === this.id) ? dmu2.name : dmu1.name;
const dmu = (dmu1.id === this.id) ? dmu2.id : dmu1.id;
this.channels[id] = [
name,
type,
lastTs,
dmu,
];
} else {
const { name } = reguser.channel[i];
this.channels[id] = [
name,
type,
lastTs,
];
}
} }
} }
if (reguser.blocked) { if (reguser.blocked) {

View File

@ -22,7 +22,6 @@ export default function audio(
case 'TOGGLE_MUTE': case 'TOGGLE_MUTE':
return { return {
...state, ...state,
// TODO error prone
mute: !state.mute, mute: !state.mute,
}; };

View File

@ -17,6 +17,7 @@ export type ChatState = {
* name, * name,
* type, * type,
* lastTs, * lastTs,
* dmUserId,
* ], * ],
* ... * ...
* } * }
@ -50,12 +51,29 @@ export default function chat(
case 'BLOCK_USER': { case 'BLOCK_USER': {
const { userId, userName } = action; const { userId, userName } = action;
const blocked = [
...state.blocked,
[userId, userName],
];
/*
* remove DM channel if exists
*/
const channels = { ...state.channels };
const chanKeys = Object.keys(channels);
for (let i = 0; i < chanKeys; i += 1) {
const cid = chanKeys[i];
if (channels[cid][1] === 1 && channels[cid][3] === userId) {
delete channels[cid];
return {
...state,
channels,
blocked,
};
}
}
return { return {
...state, ...state,
blocked: [ blocked,
...state.blocked,
[userId, userName],
],
}; };
} }

View File

@ -117,7 +117,7 @@ export default function gui(
chatRead: { chatRead: {
...state.chatRead, ...state.chatRead,
cid: Date.now(), cid: Date.now(),
} },
}; };
} }
@ -148,7 +148,7 @@ export default function gui(
case 'RECEIVE_ME': { case 'RECEIVE_ME': {
const { channels } = action; const { channels } = action;
const cids = Object.keys(channels); const cids = Object.keys(channels);
const chatRead = {...state.chatRead}; const chatRead = { ...state.chatRead };
for (let i = 0; i < cids.length; i += 1) { for (let i = 0; i < cids.length; i += 1) {
const cid = cids[i]; const cid = cids[i];
chatRead[cid] = 0; chatRead[cid] = 0;

View File

@ -2,6 +2,9 @@
import type { Action } from '../actions/types'; import type { Action } from '../actions/types';
import { createNameRegExp } from '../core/utils';
export type UserState = { export type UserState = {
name: string, name: string,
@ -32,6 +35,8 @@ export type UserState = {
notification: string, notification: string,
// 1: Admin, 0: ordinary user // 1: Admin, 0: ordinary user
userlvl: number, userlvl: number,
// regExp for detecting ping
nameRegExp: RegExp,
}; };
const initialState: UserState = { const initialState: UserState = {
@ -51,6 +56,7 @@ const initialState: UserState = {
isOnMobile: false, isOnMobile: false,
notification: null, notification: null,
userlvl: 0, userlvl: 0,
nameRegExp: null,
}; };
export default function user( export default function user(
@ -145,6 +151,7 @@ export default function user(
blockDm, blockDm,
userlvl, userlvl,
} = action; } = action;
const nameRegExp = createNameRegExp(name);
const messages = (action.messages) ? action.messages : []; const messages = (action.messages) ? action.messages : [];
return { return {
...state, ...state,
@ -158,6 +165,7 @@ export default function user(
minecraftname, minecraftname,
blockDm, blockDm,
userlvl, userlvl,
nameRegExp,
}; };
} }
@ -172,9 +180,11 @@ export default function user(
case 'SET_NAME': { case 'SET_NAME': {
const { name } = action; const { name } = action;
const nameRegExp = createNameRegExp(name);
return { return {
...state, ...state,
name, name,
nameRegExp,
}; };
} }

View File

@ -8,6 +8,7 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import logger from '../../core/logger'; import logger from '../../core/logger';
import webSockets from '../../socket/websockets';
import { RegUser, UserBlock, Channel } from '../../data/models'; import { RegUser, UserBlock, Channel } from '../../data/models';
async function block(req: Request, res: Response) { async function block(req: Request, res: Response) {
@ -108,10 +109,10 @@ async function block(req: Request, res: Response) {
if (channel) { if (channel) {
const channelId = channel.id; const channelId = channel.id;
channel.destroy(); channel.destroy();
webSockets.broadcastRemoveChatChannel(user.id, channelId, false);
webSockets.broadcastRemoveChatChannel(userId, channelId, true);
} }
// TODO notify websocket
if (ret) { if (ret) {
res.json({ res.json({
status: 'ok', status: 'ok',

View File

@ -8,6 +8,7 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import logger from '../../core/logger'; import logger from '../../core/logger';
import webSockets from '../../socket/websockets';
async function blockdm(req: Request, res: Response) { async function blockdm(req: Request, res: Response) {
const { block } = req.body; const { block } = req.body;
@ -36,7 +37,20 @@ async function blockdm(req: Request, res: Response) {
blockDm: block, blockDm: block,
}); });
// TODO notify websocket /*
* remove all dm channels
*/
const channels = user.regUser.channel;
for (let i = 0; i < channels.length; i += 1) {
const channel = channels[i];
if (channel.type === 1) {
const channelId = channel.id;
channel.destroy();
const { dmu1id, dmu2id } = channel;
webSockets.broadcastRemoveChatChannel(dmu1id, channelId, true);
webSockets.broadcastRemoveChatChannel(dmu2id, channelId, true);
}
}
res.json({ res.json({
status: 'ok', status: 'ok',

View File

@ -8,6 +8,7 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import logger from '../../core/logger'; import logger from '../../core/logger';
import webSockets from '../../socket/websockets';
async function leaveChan(req: Request, res: Response) { async function leaveChan(req: Request, res: Response) {
const channelId = parseInt(req.body.channelId, 10); const channelId = parseInt(req.body.channelId, 10);
@ -61,9 +62,11 @@ async function leaveChan(req: Request, res: Response) {
logger.info( logger.info(
`Removing user ${user.getName()} from channel ${channel.name || channelId}`, `Removing user ${user.getName()} from channel ${channel.name || channelId}`,
); );
user.regUser.removeChannel(channel); user.regUser.removeChannel(channel);
// TODO: inform websocket to remove channelId from user webSockets.broadcastRemoveChatChannel(user.id, channelId, false);
res.json({ res.json({
status: 'ok', status: 'ok',
}); });

View File

@ -8,7 +8,8 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import logger from '../../core/logger'; import logger from '../../core/logger';
import { Channel, UserChannel, RegUser } from '../../data/models'; import { ChatProvider } from '../../core/ChatProvider';
import { Channel, RegUser } from '../../data/models';
import { isUserBlockedBy } from '../../data/models/UserBlock'; import { isUserBlockedBy } from '../../data/models/UserBlock';
async function startDm(req: Request, res: Response) { async function startDm(req: Request, res: Response) {
@ -107,30 +108,18 @@ async function startDm(req: Request, res: Response) {
const ChannelId = channel[0].id; const ChannelId = channel[0].id;
const promises = [ const promises = [
UserChannel.findOrCreate({ ChatProvider.addUserToChannel(user.id, ChannelId, false),
where: { ChatProvider.addUserToChannel(userId, ChannelId, true),
UserId: dmu1id,
ChannelId,
},
raw: true,
}),
UserChannel.findOrCreate({
where: {
UserId: dmu2id,
ChannelId,
},
raw: true,
}),
]; ];
await Promise.all(promises); await Promise.all(promises);
// TODO: inform websocket to add channelId to user
res.json({ res.json({
channel: { channel: {
[ChannelId]: [ [ChannelId]: [
userName, userName,
1, 1,
Date.now(), Date.now(),
userId,
], ],
}, },
}); });

View File

@ -169,15 +169,25 @@ class ProtocolClient extends EventEmitter {
const data = JSON.parse(message); const data = JSON.parse(message);
if (Array.isArray(data)) { if (Array.isArray(data)) {
if (data.length === 5) { switch (data.length) {
// Ordinary array: Chat message case 5: {
const [name, text, country, channelId, userId] = data; // chat message
this.emit('chatMessage', name, text, country, channelId, userId); const [name, text, country, channelId, userId] = data;
this.emit('chatMessage', name, text, country, channelId, userId);
return;
}
case 2: {
// signal
const [signal, args] = data;
this.emit(signal, args);
}
default:
// nothing
} }
} else { } else {
// string = name // string = name
this.name = data; this.name = data;
this.emit('setWsName', data);
} }
} }

View File

@ -19,7 +19,7 @@ import DeRegisterMultipleChunks from './packets/DeRegisterMultipleChunks';
import ChangedMe from './packets/ChangedMe'; import ChangedMe from './packets/ChangedMe';
import OnlineCounter from './packets/OnlineCounter'; import OnlineCounter from './packets/OnlineCounter';
import chatProvider from '../core/ChatProvider'; import chatProvider, { 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';
@ -179,6 +179,52 @@ class SocketServer extends WebSocketEvents {
}); });
} }
findWsByUserId(userId) {
const { clients } = this.wss;
for (let i = 0; i < clients.length; i += 1) {
const ws = clients[i];
if (ws.user.id === userId && ws.readyState === WebSocket.OPEN) {
return ws;
}
}
return null;
}
broadcastAddChatChannel(
userId: number,
channelId: number,
channelArray: Array,
notify: boolean,
) {
const ws = this.findWsByUserId(userId);
if (ws) {
ws.user.channels[channelId] = channelArray;
const text = JSON.stringify([
'addch', {
[channelId]: channelArray,
},
]);
if (notify) {
ws.send(text);
}
}
}
broadcastRemoveChatChannel(
userId: number,
channelId: number,
notify: boolean,
) {
const ws = this.findWsByUserId(userId);
if (ws) {
delete ws.user.channels[channelId];
const text = JSON.stringify('remch', channelId);
if (notify) {
ws.send(text);
}
}
}
broadcastPixelBuffer(canvasId: number, chunkid, data: Buffer) { broadcastPixelBuffer(canvasId: number, chunkid, data: Buffer) {
const frame = WebSocket.Sender.frame(data, { const frame = WebSocket.Sender.frame(data, {
readOnly: true, readOnly: true,
@ -245,6 +291,10 @@ class SocketServer extends WebSocketEvents {
} }
static async onTextMessage(text, ws) { static async onTextMessage(text, ws) {
/*
* all client -> server text messages are
* chat messages in [message, channelId] format
*/
try { try {
let message; let message;
let channelId; let channelId;
@ -263,6 +313,9 @@ class SocketServer extends WebSocketEvents {
} }
message = message.trim(); message = message.trim();
/*
* just if logged in
*/
if (ws.name && message) { if (ws.name && message) {
const { user } = ws; const { user } = ws;
const waitLeft = ws.rateLimiter.tick(); const waitLeft = ws.rateLimiter.tick();
@ -276,44 +329,38 @@ class SocketServer extends WebSocketEvents {
])); ]));
return; return;
} }
// check proxy
if (!user.isAdmin() && await cheapDetector(user.ip)) { /*
logger.info( * if DM channel, make sure that other user has DM open
`${ws.name} / ${user.ip} tried to send chat message with proxy`, * (needed because we allow user to leave one-sided
); * and auto-join on message)
ws.send(JSON.stringify([ */
'info', const dmUserId = chatProvider.checkIfDm(user, channelId);
'You can not send chat messages with a proxy', if (dmUserId) {
'il', const dmWs = this.findWsByUserId(dmUserId);
channelId, if (dmWs) {
])); const { user: dmUser } = dmWs;
return; if (!dmUser || !dmUser.userHasChannelAccess(channelId)) {
ChatProvider.addUserToChannel(
dmUserId,
channelId,
[ws.name, 1, Date.now(), user.id],
);
}
}
} }
//
/*
* send chat message
*/
const errorMsg = await chatProvider.sendMessage( const errorMsg = await chatProvider.sendMessage(
user, user,
message, message,
channelId, channelId,
); );
if (!errorMsg) { if (errorMsg) {
// automute on repeated message spam
if (ws.last_message && ws.last_message === message) {
ws.message_repeat += 1;
if (ws.message_repeat >= 4) {
logger.info(`User ${ws.name} got automuted`);
chatProvider.automute(ws.name, channelId);
ws.message_repeat = 0;
}
} else {
ws.message_repeat = 0;
ws.last_message = message;
}
} else {
ws.send(JSON.stringify(['info', errorMsg, 'il', channelId])); ws.send(JSON.stringify(['info', errorMsg, 'il', channelId]));
} }
logger.info(
`Received chat message ${message} from ${ws.name} / ${ws.user.ip}`,
);
} else { } else {
logger.info('Got empty message or message from unidentified ws'); logger.info('Got empty message or message from unidentified ws');
} }

View File

@ -24,6 +24,21 @@ class WebSocketEvents {
broadcastMinecraftLink(name: string, minecraftid: string, accepted: boolean) { broadcastMinecraftLink(name: string, minecraftid: string, accepted: boolean) {
} }
broadcastAddChatChannel(
userId: number,
channelId: number,
channelArray: Array,
notify: boolean,
) {
}
broadcastRemoveChatChannel(
userId: number,
channelId: number,
notify: boolean,
) {
}
notifyChangedMe(name: string) { notifyChangedMe(name: string) {
} }

View File

@ -89,6 +89,50 @@ class WebSockets {
); );
} }
/*
* broadcast Assigning chat channel to user
* @param userId numerical id of user
* @param channelId numerical id of chat channel
* @param channelArray array with channel info [name, type, lastTs]
* @param notify if user should get notified over websocket
* (i.e. false if the user already gets it via api response)
*/
broadcastAddChatChannel(
userId: number,
channelId: number,
channelArray: Array,
notify: boolean = true,
) {
this.listeners.forEach(
(listener) => listener.broadcastAddChatChannel(
userId,
channelArray,
notify,
),
);
}
/*
* broadcast Removing chat channel from user
* @param userId numerical id of user
* @param channelId numerical id of chat channel
* @param notify if user should get notified over websocket
* (i.e. false if the user already gets it via api response)
*/
broadcastRemoveChatChannel(
userId: number,
channelId: number,
notify: boolean = true,
) {
this.listeners.forEach(
(listener) => listener.broadcastRemoveChatChannel(
userId,
channelId,
notify,
),
);
}
/* /*
* broadcast minecraft linking to API * broadcast minecraft linking to API
* @param name pixelplanetname * @param name pixelplanetname

View File

@ -18,6 +18,10 @@ export default (store) => (next) => (action) => {
break; break;
} }
/*
* TODO
* make LOGIN / LOGOUT Actions instead of comparing name changes
*/
case 'RECEIVE_ME': { case 'RECEIVE_ME': {
const { name } = action; const { name } = action;
ProtocolClient.setName(name); ProtocolClient.setName(name);