store country mutes in redis and make them channel specific
This commit is contained in:
parent
ecbcbe43fd
commit
dd757b035e
|
@ -3,7 +3,6 @@
|
|||
*/
|
||||
import { Op } from 'sequelize';
|
||||
import logger from './logger';
|
||||
import redis from '../data/redis/client';
|
||||
import RateLimiter from '../utils/RateLimiter';
|
||||
import {
|
||||
Channel, RegUser, UserChannel, Message,
|
||||
|
@ -11,7 +10,14 @@ import {
|
|||
import { findIdByNameOrId } from '../data/sql/RegUser';
|
||||
import ChatMessageBuffer from './ChatMessageBuffer';
|
||||
import socketEvents from '../socket/socketEvents';
|
||||
import checkIPAllowed from './isAllowed';
|
||||
import isIPAllowed from './isAllowed';
|
||||
import {
|
||||
mutec, unmutec,
|
||||
unmutecAll, listMutec,
|
||||
mute,
|
||||
unmute,
|
||||
allowedChat,
|
||||
} from '../data/redis/chat';
|
||||
import { DailyCron } from '../utils/cron';
|
||||
import { escapeMd } from './utils';
|
||||
import ttags from './ttag';
|
||||
|
@ -49,31 +55,12 @@ export class ChatProvider {
|
|||
this.apiSocketUserId = 1;
|
||||
this.caseCheck = /^[A-Z !.]*$/;
|
||||
this.cyrillic = /[\u0436-\u043B]'/;
|
||||
this.filters = [
|
||||
{
|
||||
regexp: /ADMIN/gi,
|
||||
matches: 4,
|
||||
},
|
||||
{
|
||||
regexp: /ADMlN/gi,
|
||||
matches: 4,
|
||||
},
|
||||
{
|
||||
regexp: /ADMlN/gi,
|
||||
matches: 4,
|
||||
},
|
||||
{
|
||||
regexp: /FUCK/gi,
|
||||
matches: 4,
|
||||
},
|
||||
];
|
||||
this.substitutes = [
|
||||
{
|
||||
regexp: /http[s]?:\/\/(old.)?pixelplanet\.fun\/#/g,
|
||||
replace: '#',
|
||||
},
|
||||
];
|
||||
this.mutedCountries = [];
|
||||
this.chatMessageBuffer = new ChatMessageBuffer(socketEvents);
|
||||
this.clearOldMessages = this.clearOldMessages.bind(this);
|
||||
|
||||
|
@ -274,7 +261,7 @@ export class ChatProvider {
|
|||
return this.chatMessageBuffer.getMessages(cid, limit);
|
||||
}
|
||||
|
||||
adminCommands(message, channelId, user) {
|
||||
async adminCommands(message, channelId, user) {
|
||||
// admin commands
|
||||
const cmdArr = message.split(' ');
|
||||
const cmd = cmdArr[0].substring(1);
|
||||
|
@ -314,16 +301,21 @@ export class ChatProvider {
|
|||
case 'mutec': {
|
||||
if (args[0]) {
|
||||
const cc = args[0].toLowerCase();
|
||||
if (cc.length > 3) {
|
||||
const ret = await mutec(channelId, cc);
|
||||
if (ret === null) {
|
||||
return 'No legit country defined';
|
||||
}
|
||||
this.mutedCountries.push(cc);
|
||||
this.broadcastChatMessage(
|
||||
'info',
|
||||
`Country ${cc} has been muted from en by ${initiator}`,
|
||||
channelId,
|
||||
this.infoUserId,
|
||||
);
|
||||
if (!ret) {
|
||||
return `Cuntry ${cc} is already muted`;
|
||||
}
|
||||
if (ret) {
|
||||
this.broadcastChatMessage(
|
||||
'info',
|
||||
`Country ${cc} has been muted from en by ${initiator}`,
|
||||
channelId,
|
||||
this.infoUserId,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return 'No country defined for mutec';
|
||||
|
@ -332,10 +324,13 @@ export class ChatProvider {
|
|||
case 'unmutec': {
|
||||
if (args[0]) {
|
||||
const cc = args[0].toLowerCase();
|
||||
if (!this.mutedCountries.includes(cc)) {
|
||||
return `Country ${cc} is not muted`;
|
||||
const ret = await unmutec(channelId, cc);
|
||||
if (ret === null) {
|
||||
return 'No legit country defined';
|
||||
}
|
||||
if (!ret) {
|
||||
return `Cuntry ${cc} is not muted`;
|
||||
}
|
||||
this.mutedCountries = this.mutedCountries.filter((c) => c !== cc);
|
||||
this.broadcastChatMessage(
|
||||
'info',
|
||||
`Country ${cc} has been unmuted from en by ${initiator}`,
|
||||
|
@ -344,17 +339,25 @@ export class ChatProvider {
|
|||
);
|
||||
return null;
|
||||
}
|
||||
if (this.mutedCountries.length) {
|
||||
const ret = await unmutecAll(channelId);
|
||||
if (ret) {
|
||||
this.broadcastChatMessage(
|
||||
'info',
|
||||
`Countries ${this.mutedCountries} unmuted from en by ${initiator}`,
|
||||
`All countries unmuted from this channel by ${initiator}`,
|
||||
channelId,
|
||||
this.infoUserId,
|
||||
);
|
||||
this.mutedCountries = [];
|
||||
return null;
|
||||
}
|
||||
return 'No country is currently muted';
|
||||
return 'No country is currently muted from this channel';
|
||||
}
|
||||
|
||||
case 'listmc': {
|
||||
const ccArr = await listMutec(channelId);
|
||||
if (ccArr.length) {
|
||||
return `Muted countries: ${ccArr}`;
|
||||
}
|
||||
return 'No country is currently muted from this channel';
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -382,25 +385,44 @@ export class ChatProvider {
|
|||
if (!name || !id) {
|
||||
return null;
|
||||
}
|
||||
const country = user.regUser.flag || 'xx';
|
||||
|
||||
const allowed = await checkIPAllowed(user.ip);
|
||||
if (!allowed.allowed) {
|
||||
logger.info(
|
||||
`${name} / ${user.ip} tried to send chat message but is not allowed`,
|
||||
if (!user.userlvl) {
|
||||
const [allowed, needProxycheck] = await allowedChat(
|
||||
channelId,
|
||||
id,
|
||||
user.ipSub,
|
||||
country,
|
||||
);
|
||||
switch (allowed.status) {
|
||||
case 1:
|
||||
console.log(allowed, needProxycheck, name, id, country);
|
||||
if (allowed) {
|
||||
logger.info(
|
||||
`${name} / ${user.ip} tried to send chat message but is not allowed`,
|
||||
);
|
||||
if (allowed === 1) {
|
||||
return t`You can not send chat messages with proxy`;
|
||||
case 2:
|
||||
} if (allowed === 100 && user.userlvl === 0) {
|
||||
return t`Your country is temporary muted from this chat channel`;
|
||||
} if (allowed === 101) {
|
||||
// eslint-disable-next-line max-len
|
||||
return t`You are permanently muted, join our guilded to apppeal the mute`;
|
||||
} if (allowed === 2) {
|
||||
return t`You are banned`;
|
||||
case 3:
|
||||
} if (allowed === 3) {
|
||||
return t`Your Internet Provider is banned`;
|
||||
default:
|
||||
return t`You are not allowed to use chat`;
|
||||
} if (allowed < 0) {
|
||||
const ttl = -allowed;
|
||||
if (ttl > 120) {
|
||||
const timeMin = Math.round(ttl / 60);
|
||||
return t`You are muted for another ${timeMin} minutes`;
|
||||
}
|
||||
return t`You are muted for another ${ttl} seconds`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message.charAt(0) === '/' && user.userlvl) {
|
||||
if (needProxycheck) {
|
||||
isIPAllowed(user.ip);
|
||||
}
|
||||
} else if (message.charAt(0) === '/') {
|
||||
return this.adminCommands(message, channelId, user);
|
||||
}
|
||||
|
||||
|
@ -418,7 +440,6 @@ export class ChatProvider {
|
|||
return t`You don\'t have access to this channel`;
|
||||
}
|
||||
|
||||
const country = user.regUser.flag || 'xx';
|
||||
let displayCountry = country;
|
||||
if (user.userlvl !== 0) {
|
||||
displayCountry = 'zz';
|
||||
|
@ -436,27 +457,6 @@ export class ChatProvider {
|
|||
return t`Your mail has to be verified in order to chat`;
|
||||
}
|
||||
|
||||
const muted = await ChatProvider.checkIfMuted(user.id);
|
||||
if (muted === -1) {
|
||||
return t`You are permanently muted, join our guilded to apppeal the mute`;
|
||||
}
|
||||
if (muted > 0) {
|
||||
if (muted > 120) {
|
||||
const timeMin = Math.round(muted / 60);
|
||||
return t`You are muted for another ${timeMin} minutes`;
|
||||
}
|
||||
return t`You are muted for another ${muted} seconds`;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.filters.length; i += 1) {
|
||||
const filter = this.filters[i];
|
||||
const count = (message.match(filter.regexp) || []).length;
|
||||
if (count >= filter.matches) {
|
||||
this.mute(name, { duration: 30, printChannel: channelId });
|
||||
return t`Ow no! Spam protection decided to mute you`;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.substitutes.length; i += 1) {
|
||||
const subsitute = this.substitutes[i];
|
||||
message = message.replace(subsitute.regexp, subsitute.replace);
|
||||
|
@ -471,13 +471,6 @@ export class ChatProvider {
|
|||
return t`Please use int channel`;
|
||||
}
|
||||
|
||||
if (channelId === this.enChannelId
|
||||
&& this.mutedCountries.includes(country)
|
||||
&& user.userlvl === 0
|
||||
) {
|
||||
return t`Your country is temporary muted from this chat channel`;
|
||||
}
|
||||
|
||||
if (user.last_message && user.last_message === message) {
|
||||
user.message_repeat += 1;
|
||||
if (user.message_repeat >= 4) {
|
||||
|
@ -507,12 +500,6 @@ export class ChatProvider {
|
|||
return this.chatMessageBuffer.broadcastChatMessage(...args);
|
||||
}
|
||||
|
||||
static async checkIfMuted(uid) {
|
||||
const key = `mute:${uid}`;
|
||||
const ttl = await redis.ttl(key);
|
||||
return ttl;
|
||||
}
|
||||
|
||||
async mute(nameOrId, opts) {
|
||||
const timeMin = opts.duration || null;
|
||||
const initiator = opts.initiator || null;
|
||||
|
@ -525,13 +512,9 @@ export class ChatProvider {
|
|||
const { name, id } = searchResult;
|
||||
const userPing = `@[${escapeMd(name)}](${id})`;
|
||||
|
||||
const key = `mute:${id}`;
|
||||
if (timeMin) {
|
||||
const ttl = timeMin * 60;
|
||||
await redis.set(key, '', {
|
||||
EX: ttl,
|
||||
});
|
||||
if (printChannel) {
|
||||
mute(id, timeMin);
|
||||
if (printChannel) {
|
||||
if (timeMin) {
|
||||
this.broadcastChatMessage(
|
||||
'info',
|
||||
(initiator)
|
||||
|
@ -540,10 +523,7 @@ export class ChatProvider {
|
|||
printChannel,
|
||||
this.infoUserId,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await redis.set(key, '');
|
||||
if (printChannel) {
|
||||
} else {
|
||||
this.broadcastChatMessage(
|
||||
'info',
|
||||
(initiator)
|
||||
|
@ -569,9 +549,8 @@ export class ChatProvider {
|
|||
const { name, id } = searchResult;
|
||||
const userPing = `@[${escapeMd(name)}](${id})`;
|
||||
|
||||
const key = `mute:${id}`;
|
||||
const delKeys = await redis.del(key);
|
||||
if (delKeys !== 1) {
|
||||
const succ = await unmute(id);
|
||||
if (!succ) {
|
||||
return `User ${userPing} is not muted`;
|
||||
}
|
||||
if (printChannel) {
|
||||
|
|
115
src/data/redis/chat.js
Normal file
115
src/data/redis/chat.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* chat mutes
|
||||
*/
|
||||
import client from './client';
|
||||
import { PREFIX as ALLOWED_PREFIX } from './isAllowedCache';
|
||||
|
||||
const MUTE_PREFIX = 'MUTE_PREFIX';
|
||||
const MUTEC_PREFIX = 'MUTE_PREFIXc';
|
||||
|
||||
/*
|
||||
* check if user can send chat message in channel
|
||||
*/
|
||||
export async function allowedChat(
|
||||
channelId,
|
||||
userId,
|
||||
ip,
|
||||
cc,
|
||||
) {
|
||||
if (!cc || cc.length !== 2) {
|
||||
return [0, 0];
|
||||
}
|
||||
const mutecKey = `${MUTEC_PREFIX}:${channelId}`;
|
||||
const muteKey = `${MUTE_PREFIX}:${userId}`;
|
||||
const isalKey = `${ALLOWED_PREFIX}:${ip}`;
|
||||
return client.allowedChat(
|
||||
mutecKey, muteKey, isalKey,
|
||||
cc,
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* check if user is muted
|
||||
*/
|
||||
export async function checkIfMuted(userId) {
|
||||
const key = `${MUTE_PREFIX}:${userId}`;
|
||||
const ttl = await client.ttl(key);
|
||||
return ttl;
|
||||
}
|
||||
|
||||
/*
|
||||
* mute user
|
||||
* @param userId
|
||||
* @param ttl mute time in minutes
|
||||
*/
|
||||
export function mute(userId, ttl) {
|
||||
const key = `${MUTE_PREFIX}:${userId}`;
|
||||
if (ttl) {
|
||||
return client.set(key, '', {
|
||||
EX: ttl * 60,
|
||||
});
|
||||
}
|
||||
return client.set(key, '');
|
||||
}
|
||||
|
||||
/*
|
||||
* unmute user
|
||||
* @param userId
|
||||
* @return boolean for success
|
||||
*/
|
||||
export async function unmute(userId) {
|
||||
const key = `${MUTE_PREFIX}:${userId}`;
|
||||
const ret = await client.del(key);
|
||||
return ret !== 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* mute country from channel
|
||||
* @param channelId
|
||||
* @param cc country code
|
||||
* @returns 1 if muted, 0 if already was muted, null if invalid
|
||||
*/
|
||||
export function mutec(channelId, cc) {
|
||||
if (!cc || cc.length !== 2) {
|
||||
return null;
|
||||
}
|
||||
const key = `${MUTEC_PREFIX}:${channelId}`;
|
||||
return client.hSetNX(key, cc, '');
|
||||
}
|
||||
|
||||
/*
|
||||
* unmute country from channel
|
||||
* @param channelId
|
||||
* @param cc country code
|
||||
* @return boolean if unmute successful, null if invalid
|
||||
*/
|
||||
export async function unmutec(channelId, cc) {
|
||||
if (!cc || cc.length !== 2) {
|
||||
return null;
|
||||
}
|
||||
const key = `${MUTEC_PREFIX}:${channelId}`;
|
||||
const ret = await client.hDel(key, cc, '');
|
||||
return ret !== 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* unmute all countries from channel
|
||||
* @param channelId
|
||||
* @return boolean for success
|
||||
*/
|
||||
export async function unmutecAll(channelId) {
|
||||
const key = `${MUTEC_PREFIX}:${channelId}`;
|
||||
const ret = await client.del(key);
|
||||
return ret !== 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* get list of muted countries
|
||||
* @param channelId
|
||||
* @return array with country codes that are muted
|
||||
*/
|
||||
export async function listMutec(channelId) {
|
||||
const key = `${MUTEC_PREFIX}:${channelId}`;
|
||||
const ret = await client.hKeys(key);
|
||||
return ret;
|
||||
}
|
|
@ -16,6 +16,13 @@ const scripts = {
|
|||
return args.map((a) => ((typeof a === 'string') ? a : a.toString()));
|
||||
},
|
||||
}),
|
||||
allowedChat: defineScript({
|
||||
NUMBER_OF_KEYS: 3,
|
||||
SCRIPT: fs.readFileSync('./workers/lua/allowedChat.lua'),
|
||||
transformArguments(...args) {
|
||||
return args.map((a) => ((typeof a === 'string') ? a : a.toString()));
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const client = createClient(REDIS_URL.startsWith('redis://')
|
||||
|
|
41
src/data/redis/lua/allowedChat.lua
Normal file
41
src/data/redis/lua/allowedChat.lua
Normal file
|
@ -0,0 +1,41 @@
|
|||
-- Check if user is allowed to chat
|
||||
-- Keys:
|
||||
-- mutecKey: 'mutec:cid' hash of channel for country mutes
|
||||
-- muteKey: 'mute:uid' key for user mute
|
||||
-- isalKey: 'isal:ip' (proxycheck, blacklist, whitelist)
|
||||
-- Args:
|
||||
-- cc: two letter country code of user
|
||||
-- Returns:
|
||||
-- {
|
||||
-- 1: return status code
|
||||
-- 100: country muted
|
||||
-- 101: user permanently muted
|
||||
-- >0: isAllowed status code (see core/isAllowed)
|
||||
-- 0: success
|
||||
-- <0: time left for mute in seconds * -1
|
||||
-- 2: if we have to update isAllowed (proxycheck)
|
||||
-- }
|
||||
local ret = {0, 0}
|
||||
-- check country mute
|
||||
if redis.call('hget', KEYS[1], ARGV[1]) then
|
||||
ret[1] = 100
|
||||
return ret
|
||||
end
|
||||
-- check user mute
|
||||
local ttl = redis.call('ttl', KEYS[2])
|
||||
if ttl == -1 then
|
||||
ret[1] = 101
|
||||
return ret
|
||||
end
|
||||
if ttl > 0 then
|
||||
ret[1] = -ttl
|
||||
return ret
|
||||
end
|
||||
-- check if isAllowed
|
||||
local ia = redis.call('get', KEYS[3])
|
||||
if not ia then
|
||||
ret[2] = 1
|
||||
else
|
||||
ret[1] = tonumber(ia)
|
||||
end
|
||||
return ret
|
Loading…
Reference in New Issue
Block a user