mirror of
https://github.com/hack-chat/main.git
synced 2024-03-22 13:20:33 +08:00
refactoring 2 of 2
This commit is contained in:
parent
c634e03cb5
commit
f353ecbd9e
|
@ -6,7 +6,7 @@
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// increase rate limit chance and ignore if not admin
|
// increase rate limit chance and ignore if not admin
|
||||||
if (socket.uType != 'admin') {
|
if (socket.uType != 'admin') {
|
||||||
return server._police.frisk(socket.remoteAddress, 20);
|
return server.police.frisk(socket.remoteAddress, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add new trip to config
|
// add new trip to config
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// increase rate limit chance and ignore if not admin
|
// increase rate limit chance and ignore if not admin
|
||||||
if (socket.uType != 'admin') {
|
if (socket.uType != 'admin') {
|
||||||
return server._police.frisk(socket.remoteAddress, 20);
|
return server.police.frisk(socket.remoteAddress, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
// find all users currently in a channel
|
// find all users currently in a channel
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// increase rate limit chance and ignore if not admin
|
// increase rate limit chance and ignore if not admin
|
||||||
if (socket.uType != 'admin') {
|
if (socket.uType != 'admin') {
|
||||||
return server._police.frisk(socket.remoteAddress, 20);
|
return server.police.frisk(socket.remoteAddress, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
// do command reload and store results
|
// do command reload and store results
|
||||||
let loadResult = core.dynamicImports.reloadDirCache('src/commands');
|
let loadResult = core.dynamicImports.reloadDirCache();
|
||||||
loadResult += core.commands.loadCommands();
|
loadResult += core.commands.loadCommands();
|
||||||
|
|
||||||
// clear and rebuild all module hooks
|
// clear and rebuild all module hooks
|
||||||
|
@ -18,9 +18,9 @@ exports.run = async (core, server, socket, data) => {
|
||||||
|
|
||||||
// build reply based on reload results
|
// build reply based on reload results
|
||||||
if (loadResult == '') {
|
if (loadResult == '') {
|
||||||
loadResult = `Reloaded ${core.commands._commands.length} commands, 0 errors`;
|
loadResult = `Reloaded ${core.commands.commands.length} commands, 0 errors`;
|
||||||
} else {
|
} else {
|
||||||
loadResult = `Reloaded ${core.commands._commands.length} commands, error(s):
|
loadResult = `Reloaded ${core.commands.commands.length} commands, error(s):
|
||||||
${loadResult}`;
|
${loadResult}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// increase rate limit chance and ignore if not admin
|
// increase rate limit chance and ignore if not admin
|
||||||
if (socket.uType != 'admin') {
|
if (socket.uType != 'admin') {
|
||||||
return server._police.frisk(socket.remoteAddress, 20);
|
return server.police.frisk(socket.remoteAddress, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove trip from config
|
// remove trip from config
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// increase rate limit chance and ignore if not admin
|
// increase rate limit chance and ignore if not admin
|
||||||
if (socket.uType != 'admin') {
|
if (socket.uType != 'admin') {
|
||||||
return server._police.frisk(socket.remoteAddress, 20);
|
return server.police.frisk(socket.remoteAddress, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
// attempt save, notify of failure
|
// attempt save, notify of failure
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// increase rate limit chance and ignore if not admin
|
// increase rate limit chance and ignore if not admin
|
||||||
if (socket.uType != 'admin') {
|
if (socket.uType != 'admin') {
|
||||||
return server._police.frisk(socket.remoteAddress, 20);
|
return server.police.frisk(socket.remoteAddress, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
// send text to all channels
|
// send text to all channels
|
||||||
|
|
|
@ -7,7 +7,7 @@ const verifyNickname = (nick) => /^[a-zA-Z0-9_]{1,24}$/.test(nick);
|
||||||
|
|
||||||
// module main
|
// module main
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
if (server._police.frisk(socket.remoteAddress, 6)) {
|
if (server.police.frisk(socket.remoteAddress, 6)) {
|
||||||
return server.reply({
|
return server.reply({
|
||||||
cmd: 'warn',
|
cmd: 'warn',
|
||||||
text: 'You are changing nicknames too fast. Wait a moment before trying again.'
|
text: 'You are changing nicknames too fast. Wait a moment before trying again.'
|
||||||
|
@ -31,7 +31,7 @@ exports.run = async (core, server, socket, data) => {
|
||||||
// prevent admin impersonation
|
// prevent admin impersonation
|
||||||
// TODO: prevent mod impersonation
|
// TODO: prevent mod impersonation
|
||||||
if (newNick.toLowerCase() == core.config.adminName.toLowerCase()) {
|
if (newNick.toLowerCase() == core.config.adminName.toLowerCase()) {
|
||||||
server._police.frisk(socket.remoteAddress, 4);
|
server.police.frisk(socket.remoteAddress, 4);
|
||||||
|
|
||||||
return server.reply({
|
return server.reply({
|
||||||
cmd: 'warn',
|
cmd: 'warn',
|
||||||
|
|
|
@ -24,12 +24,12 @@ exports.run = async (core, server, socket, data) => {
|
||||||
|
|
||||||
if (!text) {
|
if (!text) {
|
||||||
// lets not send objects or empty text, yea?
|
// lets not send objects or empty text, yea?
|
||||||
return server._police.frisk(socket.remoteAddress, 13);
|
return server.police.frisk(socket.remoteAddress, 13);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for spam
|
// check for spam
|
||||||
let score = text.length / 83 / 4;
|
let score = text.length / 83 / 4;
|
||||||
if (server._police.frisk(socket.remoteAddress, score)) {
|
if (server.police.frisk(socket.remoteAddress, score)) {
|
||||||
return server.reply({
|
return server.reply({
|
||||||
cmd: 'warn',
|
cmd: 'warn',
|
||||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.'
|
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.'
|
||||||
|
|
|
@ -24,12 +24,12 @@ exports.run = async (core, server, socket, payload) => {
|
||||||
|
|
||||||
if (!text) {
|
if (!text) {
|
||||||
// lets not send objects or empty text, yea?
|
// lets not send objects or empty text, yea?
|
||||||
return server._police.frisk(socket.remoteAddress, 8);
|
return server.police.frisk(socket.remoteAddress, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for spam
|
// check for spam
|
||||||
let score = text.length / 83 / 4;
|
let score = text.length / 83 / 4;
|
||||||
if (server._police.frisk(socket.remoteAddress, score)) {
|
if (server.police.frisk(socket.remoteAddress, score)) {
|
||||||
return server.reply({
|
return server.reply({
|
||||||
cmd: 'warn',
|
cmd: 'warn',
|
||||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.'
|
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.'
|
||||||
|
|
|
@ -8,7 +8,7 @@ const stripIndents = require('common-tags').stripIndents;
|
||||||
// module main
|
// module main
|
||||||
exports.run = async (core, server, socket, payload) => {
|
exports.run = async (core, server, socket, payload) => {
|
||||||
// check for spam
|
// check for spam
|
||||||
if (server._police.frisk(socket.remoteAddress, 2)) {
|
if (server.police.frisk(socket.remoteAddress, 2)) {
|
||||||
return server.reply({
|
return server.reply({
|
||||||
cmd: 'warn',
|
cmd: 'warn',
|
||||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.'
|
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.'
|
||||||
|
@ -27,7 +27,7 @@ exports.run = async (core, server, socket, payload) => {
|
||||||
API: {cmd: 'help', command: '<command name>'}`;
|
API: {cmd: 'help', command: '<command name>'}`;
|
||||||
reply += '\n\n-------------------------------------\n\n';
|
reply += '\n\n-------------------------------------\n\n';
|
||||||
|
|
||||||
let categories = core.commands.categories().sort();
|
let categories = core.commands.categoriesList.sort();
|
||||||
for (let i = 0, j = categories.length; i < j; i++) {
|
for (let i = 0, j = categories.length; i < j; i++) {
|
||||||
reply += `${categories[i].replace('../src/commands/', '').replace(/^\w/, c => c.toUpperCase())} Commands:\n`;
|
reply += `${categories[i].replace('../src/commands/', '').replace(/^\w/, c => c.toUpperCase())} Commands:\n`;
|
||||||
let catCommands = core.commands.all(categories[i]).sort((a, b) => a.info.name.localeCompare(b.info.name));
|
let catCommands = core.commands.all(categories[i]).sort((a, b) => a.info.name.localeCompare(b.info.name));
|
||||||
|
|
|
@ -8,7 +8,7 @@ const verifyNickname = (nick) => /^[a-zA-Z0-9_]{1,24}$/.test(nick);
|
||||||
// module main
|
// module main
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// check for spam
|
// check for spam
|
||||||
if (server._police.frisk(socket.remoteAddress, 2)) {
|
if (server.police.frisk(socket.remoteAddress, 2)) {
|
||||||
return server.reply({
|
return server.reply({
|
||||||
cmd: 'warn',
|
cmd: 'warn',
|
||||||
text: 'You are sending invites too fast. Wait a moment before trying again.'
|
text: 'You are sending invites too fast. Wait a moment before trying again.'
|
||||||
|
|
|
@ -55,7 +55,7 @@ exports.parseNickname = (core, data) => {
|
||||||
// module main
|
// module main
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// check for spam
|
// check for spam
|
||||||
if (server._police.frisk(socket.remoteAddress, 3)) {
|
if (server.police.frisk(socket.remoteAddress, 3)) {
|
||||||
return server.reply({
|
return server.reply({
|
||||||
cmd: 'warn',
|
cmd: 'warn',
|
||||||
text: 'You are joining channels too fast. Wait a moment and try again.'
|
text: 'You are joining channels too fast. Wait a moment and try again.'
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// module main
|
// module main
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// check for spam
|
// check for spam
|
||||||
if (server._police.frisk(socket.remoteAddress, 6)) {
|
if (server.police.frisk(socket.remoteAddress, 6)) {
|
||||||
return server.reply({
|
return server.reply({
|
||||||
cmd: 'warn',
|
cmd: 'warn',
|
||||||
text: 'You are changing channels too fast. Wait a moment before trying again.'
|
text: 'You are changing channels too fast. Wait a moment before trying again.'
|
||||||
|
|
|
@ -26,12 +26,12 @@ exports.run = async (core, server, socket, payload) => {
|
||||||
|
|
||||||
if (!text) {
|
if (!text) {
|
||||||
// lets not send objects or empty text, yea?
|
// lets not send objects or empty text, yea?
|
||||||
return server._police.frisk(socket.remoteAddress, 13);
|
return server.police.frisk(socket.remoteAddress, 13);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for spam
|
// check for spam
|
||||||
let score = text.length / 83 / 4;
|
let score = text.length / 83 / 4;
|
||||||
if (server._police.frisk(socket.remoteAddress, score)) {
|
if (server.police.frisk(socket.remoteAddress, score)) {
|
||||||
return server.reply({
|
return server.reply({
|
||||||
cmd: 'warn',
|
cmd: 'warn',
|
||||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.'
|
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.'
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
|
|
||||||
// module main
|
// module main
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
if (data.cmdKey !== server._cmdKey) {
|
if (data.cmdKey !== server.cmdKey) {
|
||||||
// internal command attempt by client, increase rate limit chance and ignore
|
// internal command attempt by client, increase rate limit chance and ignore
|
||||||
return server._police.frisk(socket.remoteAddress, 20);
|
return server.police.frisk(socket.remoteAddress, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
// send leave notice to client peers
|
// send leave notice to client peers
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
|
|
||||||
// module main
|
// module main
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
if (data.cmdKey !== server._cmdKey) {
|
if (data.cmdKey !== server.cmdKey) {
|
||||||
// internal command attempt by client, increase rate limit chance and ignore
|
// internal command attempt by client, increase rate limit chance and ignore
|
||||||
return server._police.frisk(socket.remoteAddress, 20);
|
return server.police.frisk(socket.remoteAddress, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
// send warning to target socket
|
// send warning to target socket
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// increase rate limit chance and ignore if not admin or mod
|
// increase rate limit chance and ignore if not admin or mod
|
||||||
if (socket.uType === 'user') {
|
if (socket.uType === 'user') {
|
||||||
return server._police.frisk(socket.remoteAddress, 10);
|
return server.police.frisk(socket.remoteAddress, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check user input
|
// check user input
|
||||||
|
@ -36,7 +36,7 @@ exports.run = async (core, server, socket, data) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// commit arrest record
|
// commit arrest record
|
||||||
server._police.arrest(badClient.remoteAddress, badClient.hash);
|
server.police.arrest(badClient.remoteAddress, badClient.hash);
|
||||||
|
|
||||||
console.log(`${socket.nick} [${socket.trip}] banned ${targetNick} in ${socket.channel}`);
|
console.log(`${socket.nick} [${socket.trip}] banned ${targetNick} in ${socket.channel}`);
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ exports.init = (core) => {
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// increase rate limit chance and ignore if not admin or mod
|
// increase rate limit chance and ignore if not admin or mod
|
||||||
if (socket.uType === 'user') {
|
if (socket.uType === 'user') {
|
||||||
return server._police.frisk(socket.remoteAddress, 10);
|
return server.police.frisk(socket.remoteAddress, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check user input
|
// check user input
|
||||||
|
@ -93,7 +93,7 @@ exports.chatCheck = (core, server, socket, payload) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// blanket "spam" protection, may expose the ratelimiting lines from `chat` and use that, TODO: one day #lazydev
|
// blanket "spam" protection, may expose the ratelimiting lines from `chat` and use that, TODO: one day #lazydev
|
||||||
server._police.frisk(socket.remoteAddress, 9);
|
server.police.frisk(socket.remoteAddress, 9);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// increase rate limit chance and ignore if not admin or mod
|
// increase rate limit chance and ignore if not admin or mod
|
||||||
if (socket.uType === 'user') {
|
if (socket.uType === 'user') {
|
||||||
return server._police.frisk(socket.remoteAddress, 10);
|
return server.police.frisk(socket.remoteAddress, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check user input
|
// check user input
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// increase rate limit chance and ignore if not admin or mod
|
// increase rate limit chance and ignore if not admin or mod
|
||||||
if (socket.uType === 'user') {
|
if (socket.uType === 'user') {
|
||||||
return server._police.frisk(socket.remoteAddress, 10);
|
return server.police.frisk(socket.remoteAddress, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check user input
|
// check user input
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// increase rate limit chance and ignore if not admin or mod
|
// increase rate limit chance and ignore if not admin or mod
|
||||||
if (socket.uType === 'user') {
|
if (socket.uType === 'user') {
|
||||||
return server._police.frisk(socket.remoteAddress, 10);
|
return server.police.frisk(socket.remoteAddress, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check user input
|
// check user input
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// increase rate limit chance and ignore if not admin or mod
|
// increase rate limit chance and ignore if not admin or mod
|
||||||
if (socket.uType === 'user') {
|
if (socket.uType === 'user') {
|
||||||
return server._police.frisk(socket.remoteAddress, 10);
|
return server.police.frisk(socket.remoteAddress, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check user input
|
// check user input
|
||||||
|
@ -28,7 +28,7 @@ exports.run = async (core, server, socket, data) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove arrest record
|
// remove arrest record
|
||||||
server._police.pardon(target);
|
server.police.pardon(target);
|
||||||
|
|
||||||
// mask ip if used
|
// mask ip if used
|
||||||
if (mode === 'ip') {
|
if (mode === 'ip') {
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
exports.run = async (core, server, socket, data) => {
|
exports.run = async (core, server, socket, data) => {
|
||||||
// increase rate limit chance and ignore if not admin or mod
|
// increase rate limit chance and ignore if not admin or mod
|
||||||
if (socket.uType === 'user') {
|
if (socket.uType === 'user') {
|
||||||
return server._police.frisk(socket.remoteAddress, 10);
|
return server.police.frisk(socket.remoteAddress, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove arrest records
|
// remove arrest records
|
||||||
server._police._records = {};
|
server.police.records = {};
|
||||||
|
|
||||||
console.log(`${socket.nick} [${socket.trip}] unbanned all`);
|
console.log(`${socket.nick} [${socket.trip}] unbanned all`);
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,6 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
console.log('');
|
||||||
console.log('Config generated! You may now start the server normally.');
|
console.log('Config generated! You may now start the server normally.');
|
||||||
|
console.log('');
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const didYouMean = require('didyoumean2').default;
|
const didYouMean = require('didyoumean2').default;
|
||||||
|
|
||||||
|
// default command modules path
|
||||||
|
const CmdDir = 'src/commands';
|
||||||
|
|
||||||
class CommandManager {
|
class CommandManager {
|
||||||
/**
|
/**
|
||||||
* Create a `CommandManager` instance for handling commands/protocol
|
* Create a `CommandManager` instance for handling commands/protocol
|
||||||
|
@ -18,26 +21,25 @@ class CommandManager {
|
||||||
*/
|
*/
|
||||||
constructor (core) {
|
constructor (core) {
|
||||||
this.core = core;
|
this.core = core;
|
||||||
this._commands = [];
|
this.commands = [];
|
||||||
this._categories = [];
|
this.categories = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (Re)initializes name spaces for commands and starts load routine
|
* (Re)initializes name spaces for commands and starts load routine
|
||||||
*
|
*
|
||||||
|
* @return {String} Module errors or empty if none
|
||||||
*/
|
*/
|
||||||
loadCommands () {
|
loadCommands () {
|
||||||
this._commands = [];
|
this.commands = [];
|
||||||
this._categories = [];
|
this.categories = [];
|
||||||
|
|
||||||
const core = this.core;
|
const commandImports = this.core.dynamicImports.getImport(CmdDir);
|
||||||
|
|
||||||
const commandImports = core.dynamicImports.getImport('src/commands');
|
|
||||||
let cmdErrors = '';
|
let cmdErrors = '';
|
||||||
Object.keys(commandImports).forEach(file => {
|
Object.keys(commandImports).forEach(file => {
|
||||||
let command = commandImports[file];
|
let command = commandImports[file];
|
||||||
let name = path.basename(file);
|
let name = path.basename(file);
|
||||||
cmdErrors += this._validateAndLoad(command, file, name);
|
cmdErrors += this.validateAndLoad(command, file, name);
|
||||||
});
|
});
|
||||||
|
|
||||||
return cmdErrors;
|
return cmdErrors;
|
||||||
|
@ -49,9 +51,11 @@ class CommandManager {
|
||||||
* @param {Object} command reference to the newly loaded object
|
* @param {Object} command reference to the newly loaded object
|
||||||
* @param {String} file file path to the module
|
* @param {String} file file path to the module
|
||||||
* @param {String} name command (`cmd`) name
|
* @param {String} name command (`cmd`) name
|
||||||
|
*
|
||||||
|
* @return {String} Module errors or empty if none
|
||||||
*/
|
*/
|
||||||
_validateAndLoad (command, file, name) {
|
validateAndLoad (command, file, name) {
|
||||||
let error = this._validateCommand(command);
|
let error = this.validateCommand(command);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
let errText = `Failed to load '${name}': ${error}`;
|
let errText = `Failed to load '${name}': ${error}`;
|
||||||
|
@ -70,8 +74,8 @@ class CommandManager {
|
||||||
|
|
||||||
command.info.category = category;
|
command.info.category = category;
|
||||||
|
|
||||||
if (this._categories.indexOf(category) === -1) {
|
if (this.categories.indexOf(category) === -1) {
|
||||||
this._categories.push(category);
|
this.categories.push(category);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +89,7 @@ class CommandManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._commands.push(command);
|
this.commands.push(command);
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -94,8 +98,10 @@ class CommandManager {
|
||||||
* Checks the module after having been `require()`ed in and reports errors
|
* Checks the module after having been `require()`ed in and reports errors
|
||||||
*
|
*
|
||||||
* @param {Object} object reference to the newly loaded object
|
* @param {Object} object reference to the newly loaded object
|
||||||
|
*
|
||||||
|
* @return {String} Module errors or null if none
|
||||||
*/
|
*/
|
||||||
_validateCommand (object) {
|
validateCommand (object) {
|
||||||
if (typeof object !== 'object')
|
if (typeof object !== 'object')
|
||||||
return 'command setup is invalid';
|
return 'command setup is invalid';
|
||||||
|
|
||||||
|
@ -115,27 +121,37 @@ class CommandManager {
|
||||||
* Pulls all command names from a passed `category`
|
* Pulls all command names from a passed `category`
|
||||||
*
|
*
|
||||||
* @param {String} category [Optional] filter return results by this category
|
* @param {String} category [Optional] filter return results by this category
|
||||||
|
*
|
||||||
|
* @return {Array} Array of command modules matching the category
|
||||||
*/
|
*/
|
||||||
all (category) {
|
all (category) {
|
||||||
return !category ? this._commands : this._commands.filter(c => c.info.category.toLowerCase() === category.toLowerCase());
|
return !category ? this.commands : this.commands.filter(
|
||||||
|
c => c.info.category.toLowerCase() === category.toLowerCase()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pulls all category names
|
* Pulls all category names
|
||||||
*
|
*
|
||||||
|
* @return {Array} Array of sub directories under CmdDir
|
||||||
*/
|
*/
|
||||||
categories () {
|
get categoriesList () {
|
||||||
return this._categories;
|
return this.categories;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pulls command by name or alia(s)
|
* Pulls command by name or alia(s)
|
||||||
*
|
*
|
||||||
* @param {String} name name or alias of command
|
* @param {String} name name or alias of command
|
||||||
|
*
|
||||||
|
* @return {Object} Target command module object
|
||||||
*/
|
*/
|
||||||
get (name) {
|
get (name) {
|
||||||
return this.findBy('name', name)
|
return this.findBy('name', name)
|
||||||
|| this._commands.find(command => command.info.aliases instanceof Array && command.info.aliases.indexOf(name) > -1);
|
|| this.commands.find(
|
||||||
|
command => command.info.aliases instanceof Array &&
|
||||||
|
command.info.aliases.indexOf(name) > -1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -143,9 +159,11 @@ class CommandManager {
|
||||||
*
|
*
|
||||||
* @param {String} key name or alias of command
|
* @param {String} key name or alias of command
|
||||||
* @param {String} value name or alias of command
|
* @param {String} value name or alias of command
|
||||||
|
*
|
||||||
|
* @return {Object} Target command module object
|
||||||
*/
|
*/
|
||||||
findBy (key, value) {
|
findBy (key, value) {
|
||||||
return this._commands.find(c => c.info[key] === value);
|
return this.commands.find(c => c.info[key] === value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,7 +172,9 @@ class CommandManager {
|
||||||
* @param {Object} server main server object
|
* @param {Object} server main server object
|
||||||
*/
|
*/
|
||||||
initCommandHooks (server) {
|
initCommandHooks (server) {
|
||||||
this._commands.filter(c => typeof c.initHooks !== 'undefined').forEach(c => c.initHooks(server));
|
this.commands.filter(c => typeof c.initHooks !== 'undefined').forEach(
|
||||||
|
c => c.initHooks(server)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -163,6 +183,8 @@ class CommandManager {
|
||||||
* @param {Object} server main server reference
|
* @param {Object} server main server reference
|
||||||
* @param {Object} socket calling socket reference
|
* @param {Object} socket calling socket reference
|
||||||
* @param {Object} data command structure passed by socket (client)
|
* @param {Object} data command structure passed by socket (client)
|
||||||
|
*
|
||||||
|
* @return {*} Arbitrary module return data
|
||||||
*/
|
*/
|
||||||
handleCommand (server, socket, data) {
|
handleCommand (server, socket, data) {
|
||||||
// Try to find command first
|
// Try to find command first
|
||||||
|
@ -172,7 +194,7 @@ class CommandManager {
|
||||||
return this.execute(command, server, socket, data);
|
return this.execute(command, server, socket, data);
|
||||||
} else {
|
} else {
|
||||||
// Then fail with helpful (sorta) message
|
// Then fail with helpful (sorta) message
|
||||||
return this._handleFail(server, socket, data);
|
return this.handleFail(server, socket, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,8 +204,10 @@ class CommandManager {
|
||||||
* @param {Object} server main server reference
|
* @param {Object} server main server reference
|
||||||
* @param {Object} socket calling socket reference
|
* @param {Object} socket calling socket reference
|
||||||
* @param {Object} data command structure passed by socket (client)
|
* @param {Object} data command structure passed by socket (client)
|
||||||
|
*
|
||||||
|
* @return {*} Arbitrary module return data
|
||||||
*/
|
*/
|
||||||
_handleFail(server, socket, data) {
|
handleFail (server, socket, data) {
|
||||||
const maybe = didYouMean(data.cmd, this.all().map(c => c.info.name), {
|
const maybe = didYouMean(data.cmd, this.all().map(c => c.info.name), {
|
||||||
threshold: 5,
|
threshold: 5,
|
||||||
thresholdType: 'edit-distance'
|
thresholdType: 'edit-distance'
|
||||||
|
@ -193,13 +217,17 @@ class CommandManager {
|
||||||
// Found a suggestion, pass it on to their dyslexic self
|
// Found a suggestion, pass it on to their dyslexic self
|
||||||
return this.handleCommand(server, socket, {
|
return this.handleCommand(server, socket, {
|
||||||
cmd: 'socketreply',
|
cmd: 'socketreply',
|
||||||
cmdKey: server._cmdKey,
|
cmdKey: server.cmdKey,
|
||||||
text: `Command not found, did you mean: \`${maybe}\`?`
|
text: `Command not found, did you mean: \`${maybe}\`?`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request so mangled that I don't even, silently fail
|
// Request so mangled that I don't even. . .
|
||||||
return;
|
return this.handleCommand(server, socket, {
|
||||||
|
cmd: 'socketreply',
|
||||||
|
cmdKey: server.cmdKey,
|
||||||
|
text: 'Unknown command'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -209,8 +237,10 @@ class CommandManager {
|
||||||
* @param {Object} server main server reference
|
* @param {Object} server main server reference
|
||||||
* @param {Object} socket calling socket reference
|
* @param {Object} socket calling socket reference
|
||||||
* @param {Object} data command structure passed by socket (client)
|
* @param {Object} data command structure passed by socket (client)
|
||||||
|
*
|
||||||
|
* @return {*} Arbitrary module return data
|
||||||
*/
|
*/
|
||||||
async execute(command, server, socket, data) {
|
async execute (command, server, socket, data) {
|
||||||
if (typeof command.requiredData !== 'undefined') {
|
if (typeof command.requiredData !== 'undefined') {
|
||||||
let missing = [];
|
let missing = [];
|
||||||
for (let i = 0, len = command.requiredData.length; i < len; i++) {
|
for (let i = 0, len = command.requiredData.length; i < len; i++) {
|
||||||
|
@ -219,11 +249,16 @@ class CommandManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (missing.length > 0) {
|
if (missing.length > 0) {
|
||||||
console.log(`Failed to execute '${command.info.name}': missing required ${missing.join(', ')}\n\n`);
|
console.log(`Failed to execute '${
|
||||||
|
command.info.name
|
||||||
|
}': missing required ${missing.join(', ')}\n\n`);
|
||||||
|
|
||||||
this.handleCommand(server, socket, {
|
this.handleCommand(server, socket, {
|
||||||
cmd: 'socketreply',
|
cmd: 'socketreply',
|
||||||
cmdKey: server._cmdKey,
|
cmdKey: server.cmdKey,
|
||||||
text: `Failed to execute '${command.info.name}': missing required ${missing.join(', ')}\n\n`
|
text: `Failed to execute '${
|
||||||
|
command.info.name
|
||||||
|
}': missing required ${missing.join(', ')}\n\n`
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -238,7 +273,7 @@ class CommandManager {
|
||||||
|
|
||||||
this.handleCommand(server, socket, {
|
this.handleCommand(server, socket, {
|
||||||
cmd: 'socketreply',
|
cmd: 'socketreply',
|
||||||
cmdKey: server._cmdKey,
|
cmdKey: server.cmdKey,
|
||||||
text: errText
|
text: errText
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,10 @@ class ConfigManager {
|
||||||
/**
|
/**
|
||||||
* Create a `ConfigManager` instance for managing application settings
|
* Create a `ConfigManager` instance for managing application settings
|
||||||
*
|
*
|
||||||
* @param {String} base executing directory name; __dirname
|
* @param {String} basePath executing directory name; __dirname
|
||||||
*/
|
*/
|
||||||
constructor (base = __dirname) {
|
constructor (basePath = __dirname) {
|
||||||
this.configPath = path.resolve(base, 'config/config.json');
|
this.configPath = path.resolve(basePath, 'config/config.json');
|
||||||
|
|
||||||
if (!fse.existsSync(this.configPath)){
|
if (!fse.existsSync(this.configPath)){
|
||||||
fse.ensureFileSync(this.configPath);
|
fse.ensureFileSync(this.configPath);
|
||||||
|
@ -26,10 +26,9 @@ class ConfigManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (Re)builds the config.json (main server config), or loads the config into mem
|
* Loads config.json (main server config) into mem
|
||||||
* if rebuilding, process will exit- this is to allow a process manager to take over
|
|
||||||
*
|
*
|
||||||
* @param {Boolean} reconfiguring set to true by `scripts/configure.js`, will exit if true
|
* @return {Object || Boolean} False if the config.json could not be loaded
|
||||||
*/
|
*/
|
||||||
async load () {
|
async load () {
|
||||||
try {
|
try {
|
||||||
|
@ -44,6 +43,7 @@ class ConfigManager {
|
||||||
/**
|
/**
|
||||||
* Creates backup of current config into configPath
|
* Creates backup of current config into configPath
|
||||||
*
|
*
|
||||||
|
* @return {String} Backed up config.json path
|
||||||
*/
|
*/
|
||||||
async backup () {
|
async backup () {
|
||||||
const backupPath = `${this.configPath}.${dateFormat('dd-mm-yy-HH-MM-ss')}.bak`;
|
const backupPath = `${this.configPath}.${dateFormat('dd-mm-yy-HH-MM-ss')}.bak`;
|
||||||
|
@ -56,6 +56,7 @@ class ConfigManager {
|
||||||
* First makes a backup of the current `config.json`, then writes current config
|
* First makes a backup of the current `config.json`, then writes current config
|
||||||
* to disk
|
* to disk
|
||||||
*
|
*
|
||||||
|
* @return {Boolean} False on failure
|
||||||
*/
|
*/
|
||||||
async save () {
|
async save () {
|
||||||
const backupPath = await this.backup();
|
const backupPath = await this.backup();
|
||||||
|
@ -77,6 +78,8 @@ class ConfigManager {
|
||||||
*
|
*
|
||||||
* @param {*} key arbitrary configuration key
|
* @param {*} key arbitrary configuration key
|
||||||
* @param {*} value new value to change `key` to
|
* @param {*} value new value to change `key` to
|
||||||
|
*
|
||||||
|
* @return {Boolean} False on failure
|
||||||
*/
|
*/
|
||||||
async set (key, value) {
|
async set (key, value) {
|
||||||
const realKey = `${key}`;
|
const realKey = `${key}`;
|
||||||
|
|
|
@ -45,7 +45,6 @@ class CoreApp {
|
||||||
|
|
||||||
buildImportManager () {
|
buildImportManager () {
|
||||||
this.dynamicImports = new ImportsManager(path.join(__dirname, '../..'));
|
this.dynamicImports = new ImportsManager(path.join(__dirname, '../..'));
|
||||||
this.dynamicImports.init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCommandsManager () {
|
buildCommandsManager () {
|
||||||
|
|
|
@ -14,12 +14,11 @@ class ImportsManager {
|
||||||
/**
|
/**
|
||||||
* Create a `ImportsManager` instance for (re)loading classes and config
|
* Create a `ImportsManager` instance for (re)loading classes and config
|
||||||
*
|
*
|
||||||
* @param {String} base executing directory name; __dirname
|
* @param {String} basePath executing directory name; default __dirname
|
||||||
*/
|
*/
|
||||||
constructor (base) {
|
constructor (basePath) {
|
||||||
this._base = base;
|
this.basePath = basePath;
|
||||||
|
this.imports = {};
|
||||||
this._imports = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,29 +27,18 @@ class ImportsManager {
|
||||||
* @type {String} readonly
|
* @type {String} readonly
|
||||||
*/
|
*/
|
||||||
get base () {
|
get base () {
|
||||||
return this._base;
|
return this.basePath;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize this class and start loading target directories
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
init () {
|
|
||||||
let errorText = '';
|
|
||||||
ImportsManager.load_dirs.forEach(dir => {
|
|
||||||
errorText += this.loadDir(dir);
|
|
||||||
});
|
|
||||||
|
|
||||||
return errorText;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gather all js files from target directory, then verify and load
|
* Gather all js files from target directory, then verify and load
|
||||||
*
|
*
|
||||||
* @param {String} dirName The name of the dir to load, relative to the _base path.
|
* @param {String} dirName The name of the dir to load, relative to the basePath.
|
||||||
|
*
|
||||||
|
* @return {String} Load errors or empty if none
|
||||||
*/
|
*/
|
||||||
loadDir (dirName) {
|
loadDir (dirName) {
|
||||||
const dir = path.resolve(this._base, dirName);
|
const dir = path.resolve(this.basePath, dirName);
|
||||||
|
|
||||||
let errorText = '';
|
let errorText = '';
|
||||||
try {
|
try {
|
||||||
|
@ -68,11 +56,11 @@ class ImportsManager {
|
||||||
return errorText;
|
return errorText;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._imports[dirName]) {
|
if (!this.imports[dirName]) {
|
||||||
this._imports[dirName] = {};
|
this.imports[dirName] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
this._imports[dirName][file] = imported;
|
this.imports[dirName][file] = imported;
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let err = `Unable to load modules from ${dirName}\n${e}`;
|
let err = `Unable to load modules from ${dirName}\n${e}`;
|
||||||
|
@ -88,14 +76,22 @@ class ImportsManager {
|
||||||
* Unlink references to each loaded module, pray to google that gc knows it's job,
|
* Unlink references to each loaded module, pray to google that gc knows it's job,
|
||||||
* then reinitialize this class to start the reload
|
* then reinitialize this class to start the reload
|
||||||
*
|
*
|
||||||
* @param {String} dirName The name of the dir to load, relative to the _base path.
|
* @param {Array} dirName The name of the dir to load, relative to the _base path.
|
||||||
|
*
|
||||||
|
* @return {String} Load errors or empty if none
|
||||||
*/
|
*/
|
||||||
reloadDirCache (dirName) {
|
reloadDirCache () {
|
||||||
Object.keys(this._imports[dirName]).forEach((mod) => {
|
let errorText = '';
|
||||||
delete require.cache[require.resolve(mod)];
|
|
||||||
|
Object.keys(this.imports).forEach(dir => {
|
||||||
|
Object.keys(this.imports[dir]).forEach((mod) => {
|
||||||
|
delete require.cache[require.resolve(mod)];
|
||||||
|
});
|
||||||
|
|
||||||
|
errorText += this.loadDir(dir);
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.init();
|
return errorText;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,19 +99,18 @@ class ImportsManager {
|
||||||
* load required directory if not found
|
* load required directory if not found
|
||||||
*
|
*
|
||||||
* @param {String} dirName The name of the dir to load, relative to the _base path.
|
* @param {String} dirName The name of the dir to load, relative to the _base path.
|
||||||
|
*
|
||||||
|
* @return {Object} Object containing command module paths and structs
|
||||||
*/
|
*/
|
||||||
getImport (dirName) {
|
getImport (dirName) {
|
||||||
let imported = this._imports[dirName];
|
let imported = this.imports[dirName];
|
||||||
|
|
||||||
if (!imported) {
|
if (!imported) {
|
||||||
this.loadDir(dirName);
|
this.loadDir(dirName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.assign({}, this._imports[dirName]);
|
return Object.assign({}, this.imports[dirName]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// automagically loaded directorys on instantiation
|
|
||||||
ImportsManager.load_dirs = ['src/commands'];
|
|
||||||
|
|
||||||
module.exports = ImportsManager;
|
module.exports = ImportsManager;
|
||||||
|
|
|
@ -7,15 +7,15 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const wsServer = require('ws').Server;
|
const WsServer = require('ws').Server;
|
||||||
const socketReady = require('ws').OPEN;
|
const SocketReady = require('ws').OPEN;
|
||||||
const crypto = require('crypto');
|
const Crypto = require('crypto');
|
||||||
const ipSalt = [...Array(Math.floor(Math.random()*128)+128)].map(i=>(~~(Math.random()*36)).toString(36)).join('');
|
|
||||||
const internalCmdKey = [...Array(Math.floor(Math.random()*128)+128)].map(i=>(~~(Math.random()*36)).toString(36)).join('');
|
|
||||||
const RateLimiter = require('./RateLimiter');
|
const RateLimiter = require('./RateLimiter');
|
||||||
const pulseSpeed = 16000; // ping all clients every X ms
|
const PulseSpeed = 16000; // ping all clients every X ms
|
||||||
|
const IpSalt = [...Array(Math.floor(Math.random()*128)+128)].map(i=>(~~(Math.random()*36)).toString(36)).join('');
|
||||||
|
const InternalCmdKey = [...Array(Math.floor(Math.random()*128)+128)].map(i=>(~~(Math.random()*36)).toString(36)).join('');
|
||||||
|
|
||||||
class MainServer extends wsServer {
|
class MainServer extends WsServer {
|
||||||
/**
|
/**
|
||||||
* Create a HackChat server instance.
|
* Create a HackChat server instance.
|
||||||
*
|
*
|
||||||
|
@ -24,13 +24,32 @@ class MainServer extends wsServer {
|
||||||
constructor (core) {
|
constructor (core) {
|
||||||
super({ port: core.config.websocketPort });
|
super({ port: core.config.websocketPort });
|
||||||
|
|
||||||
this._core = core;
|
this.core = core;
|
||||||
this._hooks = {};
|
this.hooks = {};
|
||||||
this._police = new RateLimiter();
|
this.police = new RateLimiter();
|
||||||
this._cmdBlacklist = {};
|
this.cmdBlacklist = {};
|
||||||
this._cmdKey = internalCmdKey;
|
|
||||||
|
|
||||||
this._heartBeat = setInterval(() => this.beatHeart(), pulseSpeed);
|
this.setupServer();
|
||||||
|
this.loadHooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal command key getter. Used to verify that internal only commands
|
||||||
|
* originate internally and not from a connected client.
|
||||||
|
* TODO: update to a structure that cannot be passed through json
|
||||||
|
*
|
||||||
|
* @type {String} readonly
|
||||||
|
*/
|
||||||
|
get cmdKey () {
|
||||||
|
return InternalCmdKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create ping interval and setup server event listeners
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
setupServer () {
|
||||||
|
this.heartBeat = setInterval(() => this.beatHeart(), PulseSpeed);
|
||||||
|
|
||||||
this.on('error', (err) => {
|
this.on('error', (err) => {
|
||||||
this.handleError('server', err);
|
this.handleError('server', err);
|
||||||
|
@ -39,8 +58,6 @@ class MainServer extends wsServer {
|
||||||
this.on('connection', (socket, request) => {
|
this.on('connection', (socket, request) => {
|
||||||
this.newConnection(socket, request);
|
this.newConnection(socket, request);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.loadHooks();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,7 +73,7 @@ class MainServer extends wsServer {
|
||||||
|
|
||||||
for (let i = 0, l = targetSockets.length; i < l; i++) {
|
for (let i = 0, l = targetSockets.length; i < l; i++) {
|
||||||
try {
|
try {
|
||||||
if (targetSockets[i].readyState === socketReady) {
|
if (targetSockets[i].readyState === SocketReady) {
|
||||||
targetSockets[i].ping();
|
targetSockets[i].ping();
|
||||||
}
|
}
|
||||||
} catch (e) { }
|
} catch (e) { }
|
||||||
|
@ -93,18 +110,18 @@ class MainServer extends wsServer {
|
||||||
*/
|
*/
|
||||||
handleData (socket, data) {
|
handleData (socket, data) {
|
||||||
// Don't penalize yet, but check whether IP is rate-limited
|
// Don't penalize yet, but check whether IP is rate-limited
|
||||||
if (this._police.frisk(socket.remoteAddress, 0)) {
|
if (this.police.frisk(socket.remoteAddress, 0)) {
|
||||||
this._core.commands.handleCommand(this, socket, {
|
this.core.commands.handleCommand(this, socket, {
|
||||||
cmd: 'socketreply',
|
cmd: 'socketreply',
|
||||||
cmdKey: this._cmdKey,
|
cmdKey: this.cmdKey,
|
||||||
text: 'Your IP is being rate-limited or blocked.'
|
text: 'You are being rate-limited or blocked.'
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Penalize here, but don't do anything about it
|
// Penalize here, but don't do anything about it
|
||||||
this._police.frisk(socket.remoteAddress, 1);
|
this.police.frisk(socket.remoteAddress, 1);
|
||||||
|
|
||||||
// Ignore ridiculously large packets
|
// Ignore ridiculously large packets
|
||||||
if (data.length > 65536) {
|
if (data.length > 65536) {
|
||||||
|
@ -112,7 +129,7 @@ class MainServer extends wsServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start sent data verification
|
// Start sent data verification
|
||||||
var payload = null;
|
let payload = null;
|
||||||
try {
|
try {
|
||||||
payload = JSON.parse(data);
|
payload = JSON.parse(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -124,6 +141,11 @@ class MainServer extends wsServer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: make this more flexible
|
||||||
|
/*
|
||||||
|
* Issue #1: hard coded `cmd` check
|
||||||
|
* Issue #2: hard coded `cmd` value checks
|
||||||
|
*/
|
||||||
if (typeof payload.cmd === 'undefined') {
|
if (typeof payload.cmd === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -136,18 +158,19 @@ class MainServer extends wsServer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof this._cmdBlacklist[payload.cmd] === 'function') {
|
if (typeof this.cmdBlacklist[payload.cmd] === 'function') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// End TODO //
|
||||||
|
|
||||||
// Execute `in` (incoming data) hooks and process results
|
// Execute `in` (incoming data) hooks and process results
|
||||||
payload = this.executeHooks('in', socket, payload);
|
payload = this.executeHooks('in', socket, payload);
|
||||||
|
|
||||||
if (typeof payload === 'string') {
|
if (typeof payload === 'string') {
|
||||||
// A hook malfunctioned, reply with error
|
// A hook malfunctioned, reply with error
|
||||||
this._core.commands.handleCommand(this, socket, {
|
this.core.commands.handleCommand(this, socket, {
|
||||||
cmd: 'socketreply',
|
cmd: 'socketreply',
|
||||||
cmdKey: this._cmdKey,
|
cmdKey: this.cmdKey,
|
||||||
text: payload
|
text: payload
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -158,7 +181,7 @@ class MainServer extends wsServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finished verification & hooks, pass to command modules
|
// Finished verification & hooks, pass to command modules
|
||||||
this._core.commands.handleCommand(this, socket, payload);
|
this.core.commands.handleCommand(this, socket, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -167,9 +190,9 @@ class MainServer extends wsServer {
|
||||||
* @param {Object} socket Closing socket object
|
* @param {Object} socket Closing socket object
|
||||||
*/
|
*/
|
||||||
handleClose (socket) {
|
handleClose (socket) {
|
||||||
this._core.commands.handleCommand(this, socket, {
|
this.core.commands.handleCommand(this, socket, {
|
||||||
cmd: 'disconnect',
|
cmd: 'disconnect',
|
||||||
cmdKey: this._cmdKey
|
cmdKey: this.cmdKey
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,9 +221,9 @@ class MainServer extends wsServer {
|
||||||
|
|
||||||
if (typeof payload === 'string') {
|
if (typeof payload === 'string') {
|
||||||
// A hook malfunctioned, reply with error
|
// A hook malfunctioned, reply with error
|
||||||
this._core.commands.handleCommand(this, socket, {
|
this.core.commands.handleCommand(this, socket, {
|
||||||
cmd: 'socketreply',
|
cmd: 'socketreply',
|
||||||
cmdKey: this._cmdKey,
|
cmdKey: this.cmdKey,
|
||||||
text: payload
|
text: payload
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -211,7 +234,7 @@ class MainServer extends wsServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (socket.readyState === socketReady) {
|
if (socket.readyState === SocketReady) {
|
||||||
socket.send(JSON.stringify(payload));
|
socket.send(JSON.stringify(payload));
|
||||||
}
|
}
|
||||||
} catch (e) { }
|
} catch (e) { }
|
||||||
|
@ -232,6 +255,8 @@ class MainServer extends wsServer {
|
||||||
*
|
*
|
||||||
* @param {Object} payload Object to convert to json for transmission
|
* @param {Object} payload Object to convert to json for transmission
|
||||||
* @param {Object} filter see `this.findSockets()`
|
* @param {Object} filter see `this.findSockets()`
|
||||||
|
*
|
||||||
|
* @return {Boolean} False if no clients matched the filter, true if data sent
|
||||||
*/
|
*/
|
||||||
broadcast (payload, filter) {
|
broadcast (payload, filter) {
|
||||||
let targetSockets = this.findSockets(filter);
|
let targetSockets = this.findSockets(filter);
|
||||||
|
@ -255,6 +280,8 @@ class MainServer extends wsServer {
|
||||||
* = {} // matches all
|
* = {} // matches all
|
||||||
* = { channel: 'programming' } // matches any socket where (`socket.channel` === 'programming')
|
* = { channel: 'programming' } // matches any socket where (`socket.channel` === 'programming')
|
||||||
* = { channel: 'programming', nick: 'Marzavec' } // matches any socket where (`socket.channel` === 'programming' && `socket.nick` === 'Marzavec')
|
* = { channel: 'programming', nick: 'Marzavec' } // matches any socket where (`socket.channel` === 'programming' && `socket.nick` === 'Marzavec')
|
||||||
|
*
|
||||||
|
* @return {Array} Clients who matched the filter requirements
|
||||||
*/
|
*/
|
||||||
findSockets (filter) {
|
findSockets (filter) {
|
||||||
let filterAttribs = Object.keys(filter);
|
let filterAttribs = Object.keys(filter);
|
||||||
|
@ -310,14 +337,16 @@ class MainServer extends wsServer {
|
||||||
* encodes and shortens the output, returns that value
|
* encodes and shortens the output, returns that value
|
||||||
*
|
*
|
||||||
* @param {Object||String} target Either the target socket or ip as string
|
* @param {Object||String} target Either the target socket or ip as string
|
||||||
|
*
|
||||||
|
* @return {String} Hashed client connection string
|
||||||
*/
|
*/
|
||||||
getSocketHash (target) {
|
getSocketHash (target) {
|
||||||
let sha = crypto.createHash('sha256');
|
let sha = Crypto.createHash('sha256');
|
||||||
|
|
||||||
if (typeof target === 'string') {
|
if (typeof target === 'string') {
|
||||||
sha.update(target + ipSalt);
|
sha.update(target + IpSalt);
|
||||||
} else {
|
} else {
|
||||||
sha.update(target.remoteAddress + ipSalt);
|
sha.update(target.remoteAddress + IpSalt);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sha.digest('base64').substr(0, 15);
|
return sha.digest('base64').substr(0, 15);
|
||||||
|
@ -332,26 +361,26 @@ class MainServer extends wsServer {
|
||||||
// clear current hooks (if any)
|
// clear current hooks (if any)
|
||||||
this.clearHooks();
|
this.clearHooks();
|
||||||
// notify each module to register their hooks (if any)
|
// notify each module to register their hooks (if any)
|
||||||
this._core.commands.initCommandHooks(this);
|
this.core.commands.initCommandHooks(this);
|
||||||
|
|
||||||
if (typeof this._hooks['in'] !== 'undefined') {
|
if (typeof this.hooks['in'] !== 'undefined') {
|
||||||
// start sorting, with incoming first
|
// start sorting, with incoming first
|
||||||
let curHooks = [ ...this._hooks['in'].keys() ];
|
let curHooks = [ ...this.hooks['in'].keys() ];
|
||||||
let hookObj = [];
|
let hookObj = [];
|
||||||
for (let i = 0, j = curHooks.length; i < j; i++) {
|
for (let i = 0, j = curHooks.length; i < j; i++) {
|
||||||
hookObj = this._hooks['in'].get(curHooks[i]);
|
hookObj = this.hooks['in'].get(curHooks[i]);
|
||||||
hookObj.sort( (h1, h2) => h1.priority - h2.priority );
|
hookObj.sort( (h1, h2) => h1.priority - h2.priority );
|
||||||
this._hooks['in'].set(hookObj);
|
this.hooks['in'].set(hookObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof this._hooks['out'] !== 'undefined') {
|
if (typeof this.hooks['out'] !== 'undefined') {
|
||||||
// then outgoing
|
// then outgoing
|
||||||
curHooks = [ ...this._hooks['out'].keys() ];
|
curHooks = [ ...this.hooks['out'].keys() ];
|
||||||
for (let i = 0, j = curHooks.length; i < j; i++) {
|
for (let i = 0, j = curHooks.length; i < j; i++) {
|
||||||
hookObj = this._hooks['out'].get(curHooks[i]);
|
hookObj = this.hooks['out'].get(curHooks[i]);
|
||||||
hookObj.sort( (h1, h2) => h1.priority - h2.priority );
|
hookObj.sort( (h1, h2) => h1.priority - h2.priority );
|
||||||
this._hooks['out'].set(hookObj);
|
this.hooks['out'].set(hookObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -371,15 +400,15 @@ class MainServer extends wsServer {
|
||||||
priority = 25;
|
priority = 25;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof this._hooks[type] === 'undefined') {
|
if (typeof this.hooks[type] === 'undefined') {
|
||||||
this._hooks[type] = new Map();
|
this.hooks[type] = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._hooks[type].has(command)) {
|
if (!this.hooks[type].has(command)) {
|
||||||
this._hooks[type].set(command, []);
|
this.hooks[type].set(command, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._hooks[type].get(command).push({
|
this.hooks[type].get(command).push({
|
||||||
run: hookFunction,
|
run: hookFunction,
|
||||||
priority: priority
|
priority: priority
|
||||||
});
|
});
|
||||||
|
@ -395,17 +424,19 @@ class MainServer extends wsServer {
|
||||||
* @param {String} type The type of event, typically `in` (incoming) or `out` (outgoing)
|
* @param {String} type The type of event, typically `in` (incoming) or `out` (outgoing)
|
||||||
* @param {Object} socket Either the target client or the client triggering the hook (depending on `type`)
|
* @param {Object} socket Either the target client or the client triggering the hook (depending on `type`)
|
||||||
* @param {Object} payload Either incoming data from client or outgoing data (depending on `type`)
|
* @param {Object} payload Either incoming data from client or outgoing data (depending on `type`)
|
||||||
|
*
|
||||||
|
* @return {Object || Boolean}
|
||||||
*/
|
*/
|
||||||
executeHooks (type, socket, payload) {
|
executeHooks (type, socket, payload) {
|
||||||
let command = payload.cmd;
|
let command = payload.cmd;
|
||||||
|
|
||||||
if (typeof this._hooks[type] !== 'undefined') {
|
if (typeof this.hooks[type] !== 'undefined') {
|
||||||
if (this._hooks[type].has(command)) {
|
if (this.hooks[type].has(command)) {
|
||||||
let hooks = this._hooks[type].get(command);
|
let hooks = this.hooks[type].get(command);
|
||||||
|
|
||||||
for (let i = 0, j = hooks.length; i < j; i++) {
|
for (let i = 0, j = hooks.length; i < j; i++) {
|
||||||
try {
|
try {
|
||||||
payload = hooks[i].run(this._core, this, socket, payload);
|
payload = hooks[i].run(this.core, this, socket, payload);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
let errText = `Hook failure, '${type}', '${command}': ${err}`;
|
let errText = `Hook failure, '${type}', '${command}': ${err}`;
|
||||||
console.log(errText);
|
console.log(errText);
|
||||||
|
@ -425,9 +456,10 @@ class MainServer extends wsServer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wipe server hooks to make ready for module reload calls
|
* Wipe server hooks to make ready for module reload calls
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
clearHooks () {
|
clearHooks () {
|
||||||
this._hooks = {};
|
this.hooks = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,25 +13,24 @@ class RateLimiter {
|
||||||
* Create a ratelimiter instance.
|
* Create a ratelimiter instance.
|
||||||
*/
|
*/
|
||||||
constructor () {
|
constructor () {
|
||||||
this._records = {};
|
this.records = {};
|
||||||
this._halflife = 30 * 1000; // milliseconds
|
this.halflife = 30 * 1000; // milliseconds
|
||||||
this._threshold = 25;
|
this.threshold = 25;
|
||||||
this._hashes = [];
|
this.hashes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds current score by `id`
|
* Finds current score by `id`
|
||||||
*
|
*
|
||||||
* @param {String} id target id / address
|
* @param {String} id target id / address
|
||||||
* @public
|
|
||||||
*
|
*
|
||||||
* @memberof Police
|
* @return {Object} Object containing the record meta
|
||||||
*/
|
*/
|
||||||
search (id) {
|
search (id) {
|
||||||
let record = this._records[id];
|
let record = this.records[id];
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
record = this._records[id] = {
|
record = this.records[id] = {
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
score: 0
|
score: 0
|
||||||
}
|
}
|
||||||
|
@ -45,9 +44,8 @@ class RateLimiter {
|
||||||
*
|
*
|
||||||
* @param {String} id target id / address
|
* @param {String} id target id / address
|
||||||
* @param {Number} deltaScore amount to adjust current score by
|
* @param {Number} deltaScore amount to adjust current score by
|
||||||
* @public
|
|
||||||
*
|
*
|
||||||
* @memberof Police
|
* @return {Boolean} True if record threshold has been exceeded
|
||||||
*/
|
*/
|
||||||
frisk (id, deltaScore) {
|
frisk (id, deltaScore) {
|
||||||
let record = this.search(id);
|
let record = this.search(id);
|
||||||
|
@ -56,11 +54,11 @@ class RateLimiter {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
record.score *= Math.pow(2, -(Date.now() - record.time ) / this._halflife);
|
record.score *= Math.pow(2, -(Date.now() - record.time ) / this.halflife);
|
||||||
record.score += deltaScore;
|
record.score += deltaScore;
|
||||||
record.time = Date.now();
|
record.time = Date.now();
|
||||||
|
|
||||||
if (record.score >= this._threshold) {
|
if (record.score >= this.threshold) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,28 +69,22 @@ class RateLimiter {
|
||||||
* Statically set server to no longer accept traffic from `id`
|
* Statically set server to no longer accept traffic from `id`
|
||||||
*
|
*
|
||||||
* @param {String} id target id / address
|
* @param {String} id target id / address
|
||||||
* @public
|
|
||||||
*
|
|
||||||
* @memberof Police
|
|
||||||
*/
|
*/
|
||||||
arrest (id, hash) {
|
arrest (id, hash) {
|
||||||
let record = this.search(id);
|
let record = this.search(id);
|
||||||
|
|
||||||
record.arrested = true;
|
record.arrested = true;
|
||||||
this._hashes[hash] = id;
|
this.hashes[hash] = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove statically assigned limit from `id`
|
* Remove statically assigned limit from `id`
|
||||||
*
|
*
|
||||||
* @param {String} id target id / address
|
* @param {String} id target id / address
|
||||||
* @public
|
|
||||||
*
|
|
||||||
* @memberof Police
|
|
||||||
*/
|
*/
|
||||||
pardon (id) {
|
pardon (id) {
|
||||||
if (typeof this._hashes[id] !== 'undefined') {
|
if (typeof this.hashes[id] !== 'undefined') {
|
||||||
id = this._hashes[id];
|
id = this.hashes[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
let record = this.search(id);
|
let record = this.search(id);
|
||||||
|
|
|
@ -20,6 +20,8 @@ class StatsManager {
|
||||||
* Retrieve value of arbitrary `key` reference
|
* Retrieve value of arbitrary `key` reference
|
||||||
*
|
*
|
||||||
* @param {String} key Reference to the arbitrary store name
|
* @param {String} key Reference to the arbitrary store name
|
||||||
|
*
|
||||||
|
* @return {*} Data referenced by `key`
|
||||||
*/
|
*/
|
||||||
get (key) {
|
get (key) {
|
||||||
return this.data[key];
|
return this.data[key];
|
||||||
|
|
Loading…
Reference in New Issue
Block a user