From c719020e17cb1c98da55be6cc7efe0e50ab51ffa Mon Sep 17 00:00:00 2001 From: marzavec Date: Sat, 29 Sep 2018 23:44:36 -0700 Subject: Added hooks, modules and cleaned up code --- server/src/commands/mod/ban.js | 22 ++++---- server/src/commands/mod/dumb.js | 98 +++++++++++++++++++++++++++------ server/src/commands/mod/kick.js | 18 +++---- server/src/commands/mod/moveuser.js | 105 ++++++++++++++++++++++++++++++++++++ server/src/commands/mod/speak.js | 27 ++++++---- server/src/commands/mod/unban.js | 17 +++--- server/src/commands/mod/unbanall.js | 36 +++++++++++++ 7 files changed, 264 insertions(+), 59 deletions(-) create mode 100644 server/src/commands/mod/moveuser.js create mode 100644 server/src/commands/mod/unbanall.js (limited to 'server/src/commands/mod') diff --git a/server/src/commands/mod/ban.js b/server/src/commands/mod/ban.js index 721ad27..8236136 100644 --- a/server/src/commands/mod/ban.js +++ b/server/src/commands/mod/ban.js @@ -2,12 +2,11 @@ Description: Adds the target socket's ip to the ratelimiter */ +// module main exports.run = async (core, server, socket, data) => { // increase rate limit chance and ignore if not admin or mod - if (socket.uType == 'user') { - server._police.frisk(socket.remoteAddress, 10); - - return; + if (socket.uType === 'user') { + return server._police.frisk(socket.remoteAddress, 10); } // check user input @@ -20,24 +19,20 @@ exports.run = async (core, server, socket, data) => { let badClient = server.findSockets({ channel: socket.channel, nick: targetNick }); if (badClient.length === 0) { - server.reply({ + return server.reply({ cmd: 'warn', text: 'Could not find user in channel' }, socket); - - return; } badClient = badClient[0]; // i guess banning mods or admins isn't the best idea? if (badClient.uType !== 'user') { - server.reply({ + return server.reply({ cmd: 'warn', text: 'Cannot ban other mods, how rude' }, socket); - - return; } // commit arrest record @@ -64,10 +59,11 @@ exports.run = async (core, server, socket, data) => { core.managers.stats.increment('users-banned'); }; +// module meta exports.requiredData = ['nick']; - exports.info = { name: 'ban', - usage: 'ban {nick}', - description: 'Disconnects the target nickname in the same channel as calling socket & adds to ratelimiter' + description: 'Disconnects the target nickname in the same channel as calling socket & adds to ratelimiter', + usage: ` + API: { cmd: 'ban', nick: '' }` }; diff --git a/server/src/commands/mod/dumb.js b/server/src/commands/mod/dumb.js index 675ecd6..ecb4e0d 100644 --- a/server/src/commands/mod/dumb.js +++ b/server/src/commands/mod/dumb.js @@ -1,18 +1,20 @@ /* - * Description: Make a user (spammer) dumb + * Description: Make a user (spammer) dumb (mute) * Author: simple */ +// module constructor exports.init = (core) => { + if (typeof core.muzzledHashes === 'undefined') { core.muzzledHashes = {}; -} + } +}; +// module main exports.run = async (core, server, socket, data) => { // increase rate limit chance and ignore if not admin or mod - if (socket.uType == 'user') { - server._police.frisk(socket.remoteAddress, 10); - - return; + if (socket.uType === 'user') { + return server._police.frisk(socket.remoteAddress, 10); } // check user input @@ -24,29 +26,25 @@ exports.run = async (core, server, socket, data) => { let badClient = server.findSockets({ channel: socket.channel, nick: data.nick }); if (badClient.length === 0) { - server.reply({ + return server.reply({ cmd: 'warn', text: 'Could not find user in channel' }, socket); - - return; } badClient = badClient[0]; // likely dont need this, muting mods and admins is fine if (badClient.uType !== 'user') { - server.reply({ + return server.reply({ cmd: 'warn', text: 'This trick wont work on mods and admin' }, socket); - - return; } // store hash in mute list let record = core.muzzledHashes[badClient.hash] = { - dumb:true + dumb: true } // store allies if needed @@ -59,12 +57,78 @@ exports.run = async (core, server, socket, data) => { cmd: 'info', text: `${socket.nick} muzzled ${data.nick} in ${socket.channel}, userhash: ${badClient.hash}` }, { uType: 'mod' }); -} +}; -exports.requiredData = ['nick']; +// module hook functions +exports.initHooks = (server) => { + server.registerHook('in', 'chat', this.chatCheck); + server.registerHook('in', 'invite', this.inviteCheck); + // TODO: add whisper hook, need hook priorities todo finished first +}; + +// hook incoming chat commands, shadow-prevent chat if they are muzzled +exports.chatCheck = (core, server, socket, payload) => { + if (typeof payload.text !== 'string') { + return false; + } + + if(core.muzzledHashes[socket.hash]){ + // build fake chat payload + mutedPayload = { + cmd: 'chat', + nick: socket.nick, + text: payload.text + }; + + if (socket.trip) { + mutedPayload.trip = socket.trip; + } + + // broadcast to any duplicate connections in channel + server.broadcast( mutedPayload, { channel: socket.channel, hash: socket.hash }); + // broadcast to allies, if any + if(core.muzzledHashes[socket.hash].allies){ + server.broadcast( mutedPayload, { channel: socket.channel, nick: core.muzzledHashes[socket.hash].allies }); + } + + // blanket "spam" protection, may expose the ratelimiting lines from `chat` and use that, TODO: one day #lazydev + server._police.frisk(socket.remoteAddress, 9); + + return false; + } + + return payload; +}; + +// shadow-prevent all invites from muzzled users +exports.inviteCheck = (core, server, socket, payload) => { + if (typeof payload.nick !== 'string') { + return false; + } + + if(core.muzzledHashes[socket.hash]){ + // generate common channel + let channel = Math.random().toString(36).substr(2, 8); + + // send fake reply + server.reply({ + cmd: 'info', + text: `You invited ${payload.nick} to ?${channel}` + }, socket); + + return false; + } + + return payload; +}; + +// module meta +exports.requiredData = ['nick']; exports.info = { name: 'dumb', - usage: 'dumb {nick} [allies...]', - description: 'Globally shadow mute a connection. Optional allies array will see muted messages.' + description: 'Globally shadow mute a connection. Optional allies array will see muted messages.', + usage: ` + API: { cmd: 'dumb', nick: '', allies: ['', ...] }` }; +exports.info.aliases = ['muzzle', 'mute']; diff --git a/server/src/commands/mod/kick.js b/server/src/commands/mod/kick.js index 75c0d40..0e8ee0a 100644 --- a/server/src/commands/mod/kick.js +++ b/server/src/commands/mod/kick.js @@ -2,12 +2,11 @@ Description: Forces a change on the target(s) socket's channel, then broadcasts event */ +// module main exports.run = async (core, server, socket, data) => { // increase rate limit chance and ignore if not admin or mod - if (socket.uType == 'user') { - server._police.frisk(socket.remoteAddress, 10); - - return; + if (socket.uType === 'user') { + return server._police.frisk(socket.remoteAddress, 10); } // check user input @@ -21,12 +20,10 @@ exports.run = async (core, server, socket, data) => { let badClients = server.findSockets({ channel: socket.channel, nick: data.nick }); if (badClients.length === 0) { - server.reply({ + return server.reply({ cmd: 'warn', text: 'Could not find user(s) in channel' }, socket); - - return; } // check if found targets are kickable, commit kick @@ -75,10 +72,11 @@ exports.run = async (core, server, socket, data) => { core.managers.stats.increment('users-kicked', kicked.length); }; +// module meta exports.requiredData = ['nick']; - exports.info = { name: 'kick', - usage: 'kick {nick}', - description: 'Silently forces target client(s) into another channel. `nick` may be string or array of strings' + description: 'Silently forces target client(s) into another channel. `nick` may be string or array of strings', + usage: ` + API: { cmd: 'kick', nick: '' }` }; diff --git a/server/src/commands/mod/moveuser.js b/server/src/commands/mod/moveuser.js new file mode 100644 index 0000000..e4f6c22 --- /dev/null +++ b/server/src/commands/mod/moveuser.js @@ -0,0 +1,105 @@ +/* + Description: Removes the target socket from the current channel and forces a join event in another +*/ + +// module main +exports.run = async (core, server, socket, data) => { + // increase rate limit chance and ignore if not admin or mod + if (socket.uType === 'user') { + return server._police.frisk(socket.remoteAddress, 10); + } + + // check user input + if (typeof data.nick !== 'string' || typeof data.channel !== 'string') { + return; + } + + if (data.channel === socket.channel) { + // moving them into the same channel? y u do this? + return; + } + + let badClients = server.findSockets({ channel: socket.channel, nick: data.nick }); + + if (badClients.length === 0) { + return server.reply({ + cmd: 'warn', + text: 'Could not find user in channel' + }, socket); + } + + let badClient = badClients[0]; + + if (badClient.uType !== 'user') { + return server.reply({ + cmd: 'warn', + text: 'Cannot move other mods, how rude' + }, socket); + } + + const currentNick = badClient.nick.toLowerCase(); + let userExists = server.findSockets({ + channel: data.channel, + nick: (targetNick) => targetNick.toLowerCase() === currentNick + }); + + if (userExists.length > 0) { + // That nickname is already in that channel + return; + } + + let peerList = server.findSockets({ channel: socket.channel }); + + if (peerList.length > 1) { + for (let i = 0, l = peerList.length; i < l; i++) { + server.reply({ + cmd: 'onlineRemove', + nick: peerList[i].nick + }, badClient); + + if (badClient.nick !== peerList[i].nick){ + server.reply({ + cmd: 'onlineRemove', + nick: badClient.nick + }, peerList[i]); + } + } + } + + let newPeerList = server.findSockets({ channel: data.channel }); + let moveAnnouncement = { + cmd: 'onlineAdd', + nick: badClient.nick, + trip: badClient.trip || 'null', + hash: server.getSocketHash(badClient) + }; + let nicks = []; + + for (let i = 0, l = newPeerList.length; i < l; i++) { + server.reply(moveAnnouncement, newPeerList[i]); + nicks.push(newPeerList[i].nick); + } + + nicks.push(badClient.nick); + + server.reply({ + cmd: 'onlineSet', + nicks: nicks + }, badClient); + + badClient.channel = data.channel; + + server.broadcast( { + cmd: 'info', + text: `${badClient.nick} was moved into ?${data.channel}` + }, { channel: data.channel }); +}; + +// module meta +exports.requiredData = ['nick', 'channel']; +exports.info = { + name: 'moveuser', + description: 'This will move the target user nick into another channel', + usage: ` + API: { cmd: 'moveuser', nick: '', channel: '' }` +}; diff --git a/server/src/commands/mod/speak.js b/server/src/commands/mod/speak.js index 454ca94..e2a3ef7 100644 --- a/server/src/commands/mod/speak.js +++ b/server/src/commands/mod/speak.js @@ -3,22 +3,26 @@ * Author: simple */ + // module constructor + exports.init = (core) => { + if (typeof core.muzzledHashes === 'undefined') { + core.muzzledHashes = {}; + } + }; + +// module main exports.run = async (core, server, socket, data) => { // increase rate limit chance and ignore if not admin or mod - if (socket.uType == 'user') { - server._police.frisk(socket.remoteAddress, 10); - - return; + if (socket.uType === 'user') { + return server._police.frisk(socket.remoteAddress, 10); } // check user input if (typeof data.ip !== 'string' && typeof data.hash !== 'string') { - server.reply({ + return server.reply({ cmd: 'warn', text: "hash:'targethash' or ip:'1.2.3.4' is required" }, socket); - - return; } // find target & remove mute status @@ -36,10 +40,13 @@ exports.run = async (core, server, socket, data) => { cmd: 'info', text: `${socket.nick} unmuzzled : ${target}` }, { uType: 'mod' }); -} +}; +// module meta exports.info = { name: 'speak', - usage: 'speak {[ip || hash]}', - description: 'Pardon a dumb user to be able to speak again' + description: 'Pardon a dumb user to be able to speak again', + usage: ` + API: { cmd: 'speak', ip/hash: ' { // increase rate limit chance and ignore if not admin or mod - if (socket.uType == 'user') { - server._police.frisk(socket.remoteAddress, 10); - - return; + if (socket.uType === 'user') { + return server._police.frisk(socket.remoteAddress, 10); } // check user input if (typeof data.ip !== 'string' && typeof data.hash !== 'string') { - server.reply({ + return server.reply({ cmd: 'warn', text: "hash:'targethash' or ip:'1.2.3.4' is required" }, socket); - - return; } // find target @@ -55,8 +52,10 @@ exports.run = async (core, server, socket, data) => { core.managers.stats.decrement('users-banned'); }; +// module meta exports.info = { name: 'unban', - usage: 'unban {[ip || hash]}', - description: 'Removes target ip from the ratelimiter' + description: 'Removes target ip from the ratelimiter', + usage: ` + API: { cmd: 'unban', ip/hash: '' }` }; diff --git a/server/src/commands/mod/unbanall.js b/server/src/commands/mod/unbanall.js new file mode 100644 index 0000000..a9cf682 --- /dev/null +++ b/server/src/commands/mod/unbanall.js @@ -0,0 +1,36 @@ +/* + Description: Clears all bans and ratelimits +*/ + +// module main +exports.run = async (core, server, socket, data) => { + // increase rate limit chance and ignore if not admin or mod + if (socket.uType === 'user') { + return server._police.frisk(socket.remoteAddress, 10); + } + + // remove arrest records + server._police._records = {}; + + console.log(`${socket.nick} [${socket.trip}] unbanned all`); + + // reply with success + server.reply({ + cmd: 'info', + text: `Unbanned all ip addresses` + }, socket); + + // notify mods + server.broadcast({ + cmd: 'info', + text: `${socket.nick} unbanned all ip addresses` + }, { uType: 'mod' }); +}; + +// module meta +exports.info = { + name: 'unbanall', + description: 'Clears all banned ip addresses', + usage: ` + API: { cmd: 'unbanall' }` +}; -- cgit v1.2.1