mirror of
https://github.com/hack-chat/main.git
synced 2024-03-22 13:20:33 +08:00
Syntax update and formatting tweaks
This commit is contained in:
parent
ea83e8c477
commit
f28e65ab80
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -3,13 +3,25 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.1.92 pre 2.2] - 2019-11-06
|
||||
### Added
|
||||
- (Server) `./server/src/utility/` directory
|
||||
- (Server) `Constants.js` class in `utility`
|
||||
- (Server) `esm` module to transpile ES6
|
||||
|
||||
### Changed
|
||||
- (Server) Changed ES5 styling to ES6
|
||||
- (Server) And improved source comments
|
||||
- (Server) Minor code format changes
|
||||
- (Server) Updated all dependencies (be sure to update your local copy with the new packages)
|
||||
|
||||
## [2.1.91 pre 2.2] - 2019-08-17
|
||||
### Added
|
||||
- (Client) Markdown engine
|
||||
- (Client) Imgur based image posting (through markdown)
|
||||
|
||||
### Changed
|
||||
- (Client) Removed cloudflare references making hack.chat is self-hosted again
|
||||
- (Client) Removed cloudflare references making hack.chat self-hosted again
|
||||
- (Client) The way messages are pushed, closing an xss vuln in PRs 985dd6f and 9fcb235
|
||||
- (Client) Side bar layout
|
||||
- (Client) Fixed some options not storing
|
||||
|
|
59
clientSource/package-lock.json
generated
59
clientSource/package-lock.json
generated
|
@ -5,9 +5,12 @@
|
|||
"requires": true,
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz",
|
||||
"integrity": "sha1-rDYTsdqb7RtHUQu0ZRuJMeRxRsc="
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"colors": {
|
||||
"version": "1.0.3",
|
||||
|
@ -28,14 +31,14 @@
|
|||
}
|
||||
},
|
||||
"ecstatic": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-1.4.1.tgz",
|
||||
"integrity": "sha1-Mst7b6LikNWGaGdNEV6PDD1WfWo=",
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz",
|
||||
"integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==",
|
||||
"requires": {
|
||||
"he": "^0.5.0",
|
||||
"mime": "^1.2.11",
|
||||
"he": "^1.1.1",
|
||||
"mime": "^1.6.0",
|
||||
"minimist": "^1.1.0",
|
||||
"url-join": "^1.0.0"
|
||||
"url-join": "^2.0.5"
|
||||
}
|
||||
},
|
||||
"eventemitter3": {
|
||||
|
@ -52,9 +55,9 @@
|
|||
}
|
||||
},
|
||||
"he": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-0.5.0.tgz",
|
||||
"integrity": "sha1-LAX/rvkLaOhg8/0rVO9YCYknfuI="
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
||||
},
|
||||
"http-proxy": {
|
||||
"version": "1.18.0",
|
||||
|
@ -67,20 +70,25 @@
|
|||
}
|
||||
},
|
||||
"http-server": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/http-server/-/http-server-0.9.0.tgz",
|
||||
"integrity": "sha1-jxsGvcczYY1NxCgxx7oa/04GABo=",
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/http-server/-/http-server-0.11.1.tgz",
|
||||
"integrity": "sha512-6JeGDGoujJLmhjiRGlt8yK8Z9Kl0vnl/dQoQZlc4oeqaUoAKQg94NILLfrY3oWzSyFaQCVNTcKE5PZ3cH8VP9w==",
|
||||
"requires": {
|
||||
"colors": "1.0.3",
|
||||
"corser": "~2.0.0",
|
||||
"ecstatic": "^1.4.0",
|
||||
"ecstatic": "^3.0.0",
|
||||
"http-proxy": "^1.8.1",
|
||||
"opener": "~1.4.0",
|
||||
"optimist": "0.6.x",
|
||||
"portfinder": "0.4.x",
|
||||
"portfinder": "^1.0.13",
|
||||
"union": "~0.4.3"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
|
@ -133,12 +141,13 @@
|
|||
}
|
||||
},
|
||||
"portfinder": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-0.4.0.tgz",
|
||||
"integrity": "sha1-o/+t/6/k+5jgYBqF7aJ8J86Eyh4=",
|
||||
"version": "1.0.25",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz",
|
||||
"integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==",
|
||||
"requires": {
|
||||
"async": "0.9.0",
|
||||
"mkdirp": "0.5.x"
|
||||
"async": "^2.6.2",
|
||||
"debug": "^3.1.1",
|
||||
"mkdirp": "^0.5.1"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
|
@ -160,9 +169,9 @@
|
|||
}
|
||||
},
|
||||
"url-join": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz",
|
||||
"integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg="
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz",
|
||||
"integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg="
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "0.0.3",
|
||||
|
|
|
@ -17,6 +17,6 @@
|
|||
"author": "Marzavec",
|
||||
"license": "WTFPL",
|
||||
"dependencies": {
|
||||
"http-server": "^0.9.0"
|
||||
"http-server": "^0.11.1"
|
||||
}
|
||||
}
|
||||
|
|
1971
package-lock.json
generated
1971
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -12,12 +12,13 @@
|
|||
"npm": ">= 6.7.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "pm2 start ./server/main.js --name hackchat-dev-server && cd ./clientSource && npm start && pm2 stop hackchat-dev-server",
|
||||
"start": "pm2 start ./server/main.js --node-args=\"-r esm\" --name hackchat-dev-server && cd ./clientSource && npm start && pm2 stop hackchat-dev-server",
|
||||
"postinstall": "cd ./clientSource && npm install && cd .. & cd ./server && npm install && npm run config"
|
||||
},
|
||||
"author": "Marzavec",
|
||||
"license": "WTFPL",
|
||||
"dependencies": {
|
||||
"pm2": "^3.5.1"
|
||||
"esm": "^3.2.25",
|
||||
"pm2": "^4.1.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
/**
|
||||
* HackChat main server entry point
|
||||
*
|
||||
* Version: v2.0.0
|
||||
* Developer: Marzavec ( https://github.com/marzavec )
|
||||
* License: WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @version v2.0.0
|
||||
* @license WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// import and initialize the core application
|
||||
const CoreApp = require('./src/serverLib/CoreApp');
|
||||
import { CoreApp } from './src/serverLib/CoreApp';
|
||||
|
||||
const coreApp = new CoreApp();
|
||||
coreApp.init();
|
||||
|
|
19
server/package-lock.json
generated
19
server/package-lock.json
generated
|
@ -18,9 +18,9 @@
|
|||
"integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0="
|
||||
},
|
||||
"async-limiter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
|
||||
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
|
||||
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
|
@ -104,6 +104,11 @@
|
|||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
},
|
||||
"esm": {
|
||||
"version": "3.2.25",
|
||||
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
|
||||
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
|
||||
},
|
||||
"eyes": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
|
||||
|
@ -350,11 +355,11 @@
|
|||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"ws": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz",
|
||||
"integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.2.0.tgz",
|
||||
"integrity": "sha512-+SqNqFbwTm/0DC18KYzIsMTnEWpLwJsiasW/O17la4iDRRIO9uaHbvKiAS3AHgTiuuWerK/brj4O6MYZkei9xg==",
|
||||
"requires": {
|
||||
"async-limiter": "~1.0.0"
|
||||
"async-limiter": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
"npm": ">= 5.7.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node main.js",
|
||||
"config": "node src/scripts/configure.js"
|
||||
"start": "node -r esm main.js",
|
||||
"config": "node -r esm src/scripts/configure.js"
|
||||
},
|
||||
"author": "Marzavec",
|
||||
"license": "WTFPL",
|
||||
|
@ -22,9 +22,10 @@
|
|||
"common-tags": "^1.8.0",
|
||||
"dateformat": "^3.0.3",
|
||||
"didyoumean2": "^2.0.2",
|
||||
"esm": "^3.2.25",
|
||||
"fs-extra": "^7.0.1",
|
||||
"prompt": "^1.0.0",
|
||||
"readdir-recursive": "0.0.4",
|
||||
"ws": "^6.1.4"
|
||||
"ws": "^7.2.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,26 +3,26 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket, data) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (socket.uType != 'admin') {
|
||||
return server.police.frisk(socket.remoteAddress, 20);
|
||||
if (socket.uType !== 'admin') {
|
||||
return server.police.frisk(socket.address, 20);
|
||||
}
|
||||
|
||||
// add new trip to config
|
||||
core.config.mods.push({ trip: data.trip });
|
||||
|
||||
// find targets current connections
|
||||
let newMod = server.findSockets({ trip: data.trip });
|
||||
const newMod = server.findSockets({ trip: data.trip });
|
||||
if (newMod.length !== 0) {
|
||||
for (let i = 0, l = newMod.length; i < l; i++) {
|
||||
for (let i = 0, l = newMod.length; i < l; i += 1) {
|
||||
// upgrade privilages
|
||||
newMod[i].uType = 'mod';
|
||||
|
||||
// inform new mod
|
||||
server.send({
|
||||
cmd: 'info',
|
||||
text: 'You are now a mod.'
|
||||
text: 'You are now a mod.',
|
||||
}, newMod[i]);
|
||||
}
|
||||
}
|
||||
|
@ -30,21 +30,22 @@ exports.run = async (core, server, socket, data) => {
|
|||
// return success message
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `Added mod trip: ${data.trip}, remember to run 'saveconfig' to make it permanent`
|
||||
text: `Added mod trip: ${data.trip}, remember to run 'saveconfig' to make it permanent`,
|
||||
}, socket);
|
||||
|
||||
// notify all mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Added mod: ${data.trip}`
|
||||
text: `Added mod: ${data.trip}`,
|
||||
}, { uType: 'mod' });
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['trip'];
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const requiredData = ['trip'];
|
||||
export const info = {
|
||||
name: 'addmod',
|
||||
description: 'Adds target trip to the config as a mod and upgrades the socket type',
|
||||
usage: `
|
||||
API: { cmd: 'addmod', trip: '<target trip>' }`
|
||||
API: { cmd: 'addmod', trip: '<target trip>' }`,
|
||||
};
|
||||
|
|
|
@ -3,46 +3,47 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (socket.uType != 'admin') {
|
||||
return server.police.frisk(socket.remoteAddress, 20);
|
||||
if (socket.uType !== 'admin') {
|
||||
return server.police.frisk(socket.address, 20);
|
||||
}
|
||||
|
||||
// find all users currently in a channel
|
||||
let currentUsers = server.findSockets({
|
||||
channel: (channel) => true
|
||||
const currentUsers = server.findSockets({
|
||||
channel: (channel) => true,
|
||||
});
|
||||
|
||||
// compile channel and user list
|
||||
let channels = {};
|
||||
for (let i = 0, j = currentUsers.length; i < j; i++) {
|
||||
const channels = {};
|
||||
for (let i = 0, j = currentUsers.length; i < j; i += 1) {
|
||||
if (typeof channels[currentUsers[i].channel] === 'undefined') {
|
||||
channels[currentUsers[i].channel] = [];
|
||||
}
|
||||
|
||||
|
||||
channels[currentUsers[i].channel].push(
|
||||
`[${currentUsers[i].trip||'null'}]${currentUsers[i].nick}`
|
||||
`[${currentUsers[i].trip || 'null'}]${currentUsers[i].nick}`,
|
||||
);
|
||||
}
|
||||
|
||||
// build output
|
||||
let lines = [];
|
||||
for (let channel in channels) {
|
||||
lines.push(`?${channel} ${channels[channel].join(", ")}`);
|
||||
const lines = [];
|
||||
for (const channel in channels) {
|
||||
lines.push(`?${channel} ${channels[channel].join(', ')}`);
|
||||
}
|
||||
|
||||
// send reply
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: lines.join("\n")
|
||||
text: lines.join('\n'),
|
||||
}, socket);
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const info = {
|
||||
name: 'listusers',
|
||||
description: 'Outputs all current channels and sockets in those channels',
|
||||
usage: `
|
||||
API: { cmd: 'listusers' }`
|
||||
API: { cmd: 'listusers' }`,
|
||||
};
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket, data) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (socket.uType != 'admin') {
|
||||
return server.police.frisk(socket.remoteAddress, 20);
|
||||
if (socket.uType !== 'admin') {
|
||||
return server.police.frisk(socket.address, 20);
|
||||
}
|
||||
|
||||
// do command reload and store results
|
||||
|
@ -17,7 +17,7 @@ exports.run = async (core, server, socket, data) => {
|
|||
server.loadHooks();
|
||||
|
||||
// build reply based on reload results
|
||||
if (loadResult == '') {
|
||||
if (loadResult === '') {
|
||||
loadResult = `Reloaded ${core.commands.commands.length} commands, 0 errors`;
|
||||
} else {
|
||||
loadResult = `Reloaded ${core.commands.commands.length} commands, error(s):
|
||||
|
@ -31,20 +31,21 @@ exports.run = async (core, server, socket, data) => {
|
|||
// reply with results
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: loadResult
|
||||
text: loadResult,
|
||||
}, socket);
|
||||
|
||||
// notify mods of reload #transparency
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: loadResult
|
||||
text: loadResult,
|
||||
}, { uType: 'mod' });
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const info = {
|
||||
name: 'reload',
|
||||
description: '(Re)loads any new commands into memory, outputs errors if any',
|
||||
usage: `
|
||||
API: { cmd: 'reload', reason: '<optional reason append>' }`
|
||||
API: { cmd: 'reload', reason: '<optional reason append>' }`,
|
||||
};
|
||||
|
|
|
@ -3,26 +3,26 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket, data) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (socket.uType != 'admin') {
|
||||
return server.police.frisk(socket.remoteAddress, 20);
|
||||
if (socket.uType !== 'admin') {
|
||||
return server.police.frisk(socket.address, 20);
|
||||
}
|
||||
|
||||
// remove trip from config
|
||||
core.config.mods = core.config.mods.filter(mod => mod.trip !== data.trip);
|
||||
core.config.mods = core.config.mods.filter((mod) => mod.trip !== data.trip);
|
||||
|
||||
// find targets current connections
|
||||
let targetMod = server.findSockets({ trip: data.trip });
|
||||
const targetMod = server.findSockets({ trip: data.trip });
|
||||
if (targetMod.length !== 0) {
|
||||
for (let i = 0, l = targetMod.length; i < l; i++) {
|
||||
for (let i = 0, l = targetMod.length; i < l; i += 1) {
|
||||
// downgrade privilages
|
||||
targetMod[i].uType = 'user';
|
||||
|
||||
// inform ex-mod
|
||||
server.send({
|
||||
cmd: 'info',
|
||||
text: 'You are now a user.'
|
||||
text: 'You are now a user.',
|
||||
}, targetMod[i]);
|
||||
}
|
||||
}
|
||||
|
@ -32,21 +32,22 @@ exports.run = async (core, server, socket, data) => {
|
|||
cmd: 'info',
|
||||
text: `Removed mod trip: ${
|
||||
data.trip
|
||||
}, remember to run 'saveconfig' to make it permanent`
|
||||
}, remember to run 'saveconfig' to make it permanent`,
|
||||
}, socket);
|
||||
|
||||
// notify all mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Removed mod: ${data.trip}`
|
||||
text: `Removed mod: ${data.trip}`,
|
||||
}, { uType: 'mod' });
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['trip'];
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const requiredData = ['trip'];
|
||||
export const info = {
|
||||
name: 'removemod',
|
||||
description: 'Removes target trip from the config as a mod and downgrades the socket type',
|
||||
usage: `
|
||||
API: { cmd: 'removemod', trip: '<target trip>' }`
|
||||
API: { cmd: 'removemod', trip: '<target trip>' }`,
|
||||
};
|
||||
|
|
|
@ -3,37 +3,38 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (socket.uType != 'admin') {
|
||||
return server.police.frisk(socket.remoteAddress, 20);
|
||||
if (socket.uType !== 'admin') {
|
||||
return server.police.frisk(socket.address, 20);
|
||||
}
|
||||
|
||||
// attempt save, notify of failure
|
||||
if (!core.configManager.save()) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Failed to save config, check logs.'
|
||||
}, client);
|
||||
text: 'Failed to save config, check logs.',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// return success message
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: 'Config saved!'
|
||||
text: 'Config saved!',
|
||||
}, socket);
|
||||
|
||||
// notify mods #transparency
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: 'Config saved!'
|
||||
text: 'Config saved!',
|
||||
}, { uType: 'mod' });
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const info = {
|
||||
name: 'saveconfig',
|
||||
description: 'Writes the current config to disk',
|
||||
usage: `
|
||||
API: { cmd: 'saveconfig' }`
|
||||
API: { cmd: 'saveconfig' }`,
|
||||
};
|
||||
|
|
|
@ -3,24 +3,25 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket, data) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (socket.uType != 'admin') {
|
||||
return server.police.frisk(socket.remoteAddress, 20);
|
||||
if (socket.uType !== 'admin') {
|
||||
return server.police.frisk(socket.address, 20);
|
||||
}
|
||||
|
||||
// send text to all channels
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Server Notice: ${data.text}`
|
||||
text: `Server Notice: ${data.text}`,
|
||||
}, {});
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['text'];
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const requiredData = ['text'];
|
||||
export const info = {
|
||||
name: 'shout',
|
||||
description: 'Displays passed text to every client connected',
|
||||
usage: `
|
||||
API: { cmd: 'shout', text: '<shout text>' }`
|
||||
API: { cmd: 'shout', text: '<shout text>' }`,
|
||||
};
|
||||
|
|
|
@ -6,43 +6,43 @@
|
|||
const verifyNickname = (nick) => /^[a-zA-Z0-9_]{1,24}$/.test(nick);
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
if (server.police.frisk(socket.remoteAddress, 6)) {
|
||||
export async function run(core, server, socket, data) {
|
||||
if (server.police.frisk(socket.address, 6)) {
|
||||
return server.reply({
|
||||
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.',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// verify user data is string
|
||||
if (typeof data.nick !== 'string') {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// make sure requested nickname meets standards
|
||||
let newNick = data.nick.trim();
|
||||
const newNick = data.nick.trim();
|
||||
if (!verifyNickname(newNick)) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Nickname must consist of up to 24 letters, numbers, and underscores'
|
||||
text: 'Nickname must consist of up to 24 letters, numbers, and underscores',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// prevent admin impersonation
|
||||
// TODO: prevent mod impersonation
|
||||
if (newNick.toLowerCase() == core.config.adminName.toLowerCase()) {
|
||||
server.police.frisk(socket.remoteAddress, 4);
|
||||
if (newNick.toLowerCase() === core.config.adminName.toLowerCase()) {
|
||||
server.police.frisk(socket.address, 4);
|
||||
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You are not the admin, liar!'
|
||||
text: 'You are not the admin, liar!',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// find any sockets that have the same nickname
|
||||
let userExists = server.findSockets({
|
||||
const userExists = server.findSockets({
|
||||
channel: socket.channel,
|
||||
nick: (targetNick) => targetNick.toLowerCase() === newNick.toLowerCase()
|
||||
nick: (targetNick) => targetNick.toLowerCase() === newNick.toLowerCase(),
|
||||
});
|
||||
|
||||
// return error if found
|
||||
|
@ -50,82 +50,83 @@ exports.run = async (core, server, socket, data) => {
|
|||
// That nickname is already in that channel
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Nickname taken'
|
||||
text: 'Nickname taken',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// build join and leave notices
|
||||
// TODO: this is a legacy client holdover, name changes in the future will
|
||||
// have thieir own event
|
||||
let leaveNotice = {
|
||||
const leaveNotice = {
|
||||
cmd: 'onlineRemove',
|
||||
nick: socket.nick
|
||||
nick: socket.nick,
|
||||
};
|
||||
|
||||
let joinNotice = {
|
||||
const joinNotice = {
|
||||
cmd: 'onlineAdd',
|
||||
nick: newNick,
|
||||
trip: socket.trip || 'null',
|
||||
hash: socket.hash
|
||||
hash: socket.hash,
|
||||
};
|
||||
|
||||
// broadcast remove event and join event with new name, this is to support legacy clients and bots
|
||||
server.broadcast( leaveNotice, { channel: socket.channel });
|
||||
server.broadcast( joinNotice, { channel: socket.channel });
|
||||
server.broadcast(leaveNotice, { channel: socket.channel });
|
||||
server.broadcast(joinNotice, { channel: socket.channel });
|
||||
|
||||
// notify channel that the user has changed their name
|
||||
server.broadcast( {
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick} is now ${newNick}`
|
||||
text: `${socket.nick} is now ${newNick}`,
|
||||
}, { channel: socket.channel });
|
||||
|
||||
// commit change to nickname
|
||||
socket.nick = newNick;
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// module hook functions
|
||||
exports.initHooks = (server) => {
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.nickCheck, 29);
|
||||
};
|
||||
}
|
||||
|
||||
// hooks chat commands checking for /nick
|
||||
exports.nickCheck = (core, server, socket, payload) => {
|
||||
export function nickCheck(core, server, socket, payload) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/nick')) {
|
||||
let input = payload.text.split(' ');
|
||||
const input = payload.text.split(' ');
|
||||
|
||||
// If there is no nickname target parameter
|
||||
if (input[1] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Refer to `/help nick` for instructions on how to use this command.'
|
||||
text: 'Refer to `/help nick` for instructions on how to use this command.',
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
let newNick = input[1].replace(/@/g, '');
|
||||
const newNick = input[1].replace(/@/g, '');
|
||||
|
||||
this.run(core, server, socket, {
|
||||
cmd: 'changenick',
|
||||
nick: newNick
|
||||
nick: newNick,
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
}
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['nick'];
|
||||
exports.info = {
|
||||
export const requiredData = ['nick'];
|
||||
export const info = {
|
||||
name: 'changenick',
|
||||
description: 'This will change your current connections nickname',
|
||||
usage: `
|
||||
API: { cmd: 'changenick', nick: '<new nickname>' }
|
||||
Text: /nick <new nickname>`
|
||||
Text: /nick <new nickname>`,
|
||||
};
|
||||
|
|
|
@ -9,43 +9,45 @@ const parseText = (text) => {
|
|||
return false;
|
||||
}
|
||||
|
||||
// strip newlines from beginning and end
|
||||
text = text.replace(/^\s*\n|^\s+$|\n\s*$/g, '');
|
||||
// replace 3+ newlines with just 2 newlines
|
||||
text = text.replace(/\n{3,}/g, "\n\n");
|
||||
let sanitizedText = text;
|
||||
|
||||
return text;
|
||||
// strip newlines from beginning and end
|
||||
sanitizedText = sanitizedText.replace(/^\s*\n|^\s+$|\n\s*$/g, '');
|
||||
// replace 3+ newlines with just 2 newlines
|
||||
sanitizedText = sanitizedText.replace(/\n{3,}/g, '\n\n');
|
||||
|
||||
return sanitizedText;
|
||||
};
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket, data) {
|
||||
// check user input
|
||||
let text = parseText(data.text);
|
||||
const text = parseText(data.text);
|
||||
|
||||
if (!text) {
|
||||
// lets not send objects or empty text, yea?
|
||||
return server.police.frisk(socket.remoteAddress, 13);
|
||||
return server.police.frisk(socket.address, 13);
|
||||
}
|
||||
|
||||
// check for spam
|
||||
let score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket.remoteAddress, score)) {
|
||||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket.address, score)) {
|
||||
return server.reply({
|
||||
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.',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// build chat payload
|
||||
let payload = {
|
||||
const payload = {
|
||||
cmd: 'chat',
|
||||
nick: socket.nick,
|
||||
text: text
|
||||
text,
|
||||
};
|
||||
|
||||
if (socket.uType == 'admin') {
|
||||
if (socket.uType === 'admin') {
|
||||
payload.admin = true;
|
||||
} else if (socket.uType == 'mod') {
|
||||
} else if (socket.uType === 'mod') {
|
||||
payload.mod = true;
|
||||
}
|
||||
|
||||
|
@ -54,20 +56,22 @@ exports.run = async (core, server, socket, data) => {
|
|||
}
|
||||
|
||||
// broadcast to channel peers
|
||||
server.broadcast( payload, { channel: socket.channel});
|
||||
server.broadcast(payload, { channel: socket.channel });
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('messages-sent');
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// module hook functions
|
||||
exports.initHooks = (server) => {
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.commandCheckIn, 20);
|
||||
server.registerHook('in', 'chat', this.finalCmdCheck, 254);
|
||||
};
|
||||
}
|
||||
|
||||
// checks for miscellaneous '/' based commands
|
||||
exports.commandCheckIn = (core, server, socket, payload) => {
|
||||
export function commandCheckIn(core, server, socket, payload) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
@ -75,16 +79,16 @@ exports.commandCheckIn = (core, server, socket, payload) => {
|
|||
if (payload.text.startsWith('/myhash')) {
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `Your hash: ${socket.hash}`
|
||||
text: `Your hash: ${socket.hash}`,
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
}
|
||||
|
||||
exports.finalCmdCheck = (core, server, socket, payload) => {
|
||||
export function finalCmdCheck(core, server, socket, payload) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
@ -97,26 +101,23 @@ exports.finalCmdCheck = (core, server, socket, payload) => {
|
|||
payload.text = payload.text.substr(1);
|
||||
|
||||
return payload;
|
||||
} else {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: `Unknown command: ${payload.text}`
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: `Unknown command: ${payload.text}`,
|
||||
}, socket);
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['text'];
|
||||
exports.info = {
|
||||
return false;
|
||||
}
|
||||
|
||||
export const requiredData = ['text'];
|
||||
export const info = {
|
||||
name: 'chat',
|
||||
description: 'Broadcasts passed `text` field to the calling users channel',
|
||||
usage: `
|
||||
API: { cmd: 'chat', text: '<text to send>' }
|
||||
Text: Uuuuhm. Just kind type in that little box at the bottom and hit enter.\n
|
||||
Bonus super secret hidden commands:
|
||||
/myhash`
|
||||
/myhash`,
|
||||
};
|
||||
|
|
|
@ -9,91 +9,94 @@ const parseText = (text) => {
|
|||
return false;
|
||||
}
|
||||
|
||||
// strip newlines from beginning and end
|
||||
text = text.replace(/^\s*\n|^\s+$|\n\s*$/g, '');
|
||||
// replace 3+ newlines with just 2 newlines
|
||||
text = text.replace(/\n{3,}/g, "\n\n");
|
||||
let sanitizedText = text;
|
||||
|
||||
return text;
|
||||
// strip newlines from beginning and end
|
||||
sanitizedText = sanitizedText.replace(/^\s*\n|^\s+$|\n\s*$/g, '');
|
||||
// replace 3+ newlines with just 2 newlines
|
||||
sanitizedText = sanitizedText.replace(/\n{3,}/g, '\n\n');
|
||||
|
||||
return sanitizedText;
|
||||
};
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, payload) => {
|
||||
export async function run(core, server, socket, payload) {
|
||||
// check user input
|
||||
let text = parseText(payload.text);
|
||||
const text = parseText(payload.text);
|
||||
|
||||
if (!text) {
|
||||
// lets not send objects or empty text, yea?
|
||||
return server.police.frisk(socket.remoteAddress, 8);
|
||||
return server.police.frisk(socket.address, 8);
|
||||
}
|
||||
|
||||
// check for spam
|
||||
let score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket.remoteAddress, score)) {
|
||||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket.address, score)) {
|
||||
return server.reply({
|
||||
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.',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
let newPayload = {
|
||||
const newPayload = {
|
||||
cmd: 'info',
|
||||
type: 'emote',
|
||||
nick: socket.nick,
|
||||
text: `@${socket.nick} ${text}`
|
||||
text: `@${socket.nick} ${text}`,
|
||||
};
|
||||
if (socket.trip) {
|
||||
newPayload.trip = socket.trip;
|
||||
}
|
||||
|
||||
// broadcast to channel peers
|
||||
server.broadcast( newPayload, { channel: socket.channel});
|
||||
};
|
||||
server.broadcast(newPayload, { channel: socket.channel });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// module hook functions
|
||||
exports.initHooks = (server) => {
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.emoteCheck, 30);
|
||||
};
|
||||
}
|
||||
|
||||
// hooks chat commands checking for /me
|
||||
exports.emoteCheck = (core, server, socket, payload) => {
|
||||
export function emoteCheck(core, server, socket, payload) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/me ')) {
|
||||
let input = payload.text.split(' ');
|
||||
const input = payload.text.split(' ');
|
||||
|
||||
// If there is no emote target parameter
|
||||
if (input[1] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Refer to `/help emote` for instructions on how to use this command.'
|
||||
text: 'Refer to `/help emote` for instructions on how to use this command.',
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
input.splice(0, 1);
|
||||
let actionText = input.join(' ');
|
||||
const actionText = input.join(' ');
|
||||
|
||||
this.run(core, server, socket, {
|
||||
cmd: 'emote',
|
||||
text: actionText
|
||||
text: actionText,
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
}
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['text'];
|
||||
exports.info = {
|
||||
export const requiredData = ['text'];
|
||||
export const info = {
|
||||
name: 'emote',
|
||||
description: 'Typical emote / action text',
|
||||
usage: `
|
||||
API: { cmd: 'emote', text: '<emote/action text>' }
|
||||
Text: /me <emote/action text>`
|
||||
Text: /me <emote/action text>`,
|
||||
};
|
||||
|
|
|
@ -3,21 +3,21 @@
|
|||
*/
|
||||
|
||||
// module support functions
|
||||
const stripIndents = require('common-tags').stripIndents;
|
||||
const { stripIndents } = require('common-tags');
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, payload) => {
|
||||
export async function run(core, server, socket, payload) {
|
||||
// check for spam
|
||||
if (server.police.frisk(socket.remoteAddress, 2)) {
|
||||
if (server.police.frisk(socket.address, 2)) {
|
||||
return server.reply({
|
||||
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.',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// verify user input
|
||||
if (typeof payload.command !== 'undefined' && typeof payload.command !== 'string') {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
let reply = '';
|
||||
|
@ -27,21 +27,21 @@ exports.run = async (core, server, socket, payload) => {
|
|||
API: {cmd: 'help', command: '<command name>'}`;
|
||||
reply += '\n\n-------------------------------------\n\n';
|
||||
|
||||
let categories = core.commands.categoriesList.sort();
|
||||
for (let i = 0, j = categories.length; i < j; i++) {
|
||||
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));
|
||||
reply += ` ${catCommands.map(c => `${c.info.name}`).join(', ')}\n\n`;
|
||||
const categories = core.commands.categoriesList.sort();
|
||||
for (let i = 0, j = categories.length; i < j; i += 1) {
|
||||
reply += `${categories[i].replace('../src/commands/', '').replace(/^\w/, (c) => c.toUpperCase())} Commands:\n`;
|
||||
const catCommands = core.commands.all(categories[i]).sort((a, b) => a.info.name.localeCompare(b.info.name));
|
||||
reply += ` ${catCommands.map((c) => `${c.info.name}`).join(', ')}\n\n`;
|
||||
}
|
||||
} else {
|
||||
let command = core.commands.get(payload.command);
|
||||
const command = core.commands.get(payload.command);
|
||||
|
||||
if (typeof command === 'undefined') {
|
||||
reply = 'Unknown command';
|
||||
} else {
|
||||
reply = stripIndents`Name: ${command.info.name}
|
||||
Aliases: ${typeof command.info.aliases !== 'undefined' ? command.info.aliases.join(', ') : 'None'}
|
||||
Category: ${command.info.category.replace('../src/commands/', '').replace(/^\w/, c => c.toUpperCase())}
|
||||
Category: ${command.info.category.replace('../src/commands/', '').replace(/^\w/, (c) => c.toUpperCase())}
|
||||
Required Parameters: ${command.requiredData || 'None'}\n
|
||||
Description: ${command.info.description || '¯\_(ツ)_/¯'}\n
|
||||
Usage: ${command.info.usage || command.info.name}`;
|
||||
|
@ -51,40 +51,41 @@ exports.run = async (core, server, socket, payload) => {
|
|||
// output reply
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: reply
|
||||
text: reply,
|
||||
}, socket);
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// module hook functions
|
||||
exports.initHooks = (server) => {
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.helpCheck, 28);
|
||||
};
|
||||
}
|
||||
|
||||
// hooks chat commands checking for /whisper
|
||||
exports.helpCheck = (core, server, socket, payload) => {
|
||||
export function helpCheck(core, server, socket, payload) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/help')) {
|
||||
let input = payload.text.substr(1).split(' ', 2);
|
||||
const input = payload.text.substr(1).split(' ', 2);
|
||||
|
||||
this.run(core, server, socket, {
|
||||
cmd: input[0],
|
||||
command: input[1]
|
||||
command: input[1],
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
}
|
||||
|
||||
// module meta
|
||||
exports.info = {
|
||||
export const info = {
|
||||
name: 'help',
|
||||
description: 'Outputs information about the servers current protocol',
|
||||
usage: `
|
||||
API: { cmd: 'help', command: '<optional command name>' }
|
||||
Text: /help <optional command name>`
|
||||
Text: /help <optional command name>`,
|
||||
};
|
||||
|
|
|
@ -6,47 +6,47 @@
|
|||
const verifyNickname = (nick) => /^[a-zA-Z0-9_]{1,24}$/.test(nick);
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket, data) {
|
||||
// check for spam
|
||||
if (server.police.frisk(socket.remoteAddress, 2)) {
|
||||
if (server.police.frisk(socket.address, 2)) {
|
||||
return server.reply({
|
||||
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.',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// verify user input
|
||||
if (typeof data.nick !== 'string' || !verifyNickname(data.nick)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// why would you invite yourself?
|
||||
if (data.nick == socket.nick) {
|
||||
return;
|
||||
if (data.nick === socket.nick) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// generate common channel
|
||||
let channel = Math.random().toString(36).substr(2, 8);
|
||||
const channel = Math.random().toString(36).substr(2, 8);
|
||||
|
||||
// build and send invite
|
||||
let payload = {
|
||||
const payload = {
|
||||
cmd: 'info',
|
||||
type: 'invite',
|
||||
from: socket.nick,
|
||||
invite: channel,
|
||||
text: `${socket.nick} invited you to ?${channel}`
|
||||
text: `${socket.nick} invited you to ?${channel}`,
|
||||
};
|
||||
|
||||
let inviteSent = server.broadcast( payload, {
|
||||
const inviteSent = server.broadcast(payload, {
|
||||
channel: socket.channel,
|
||||
nick: data.nick
|
||||
nick: data.nick,
|
||||
});
|
||||
|
||||
// server indicates the user was not found
|
||||
if (!inviteSent) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in channel'
|
||||
text: 'Could not find user in channel',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
|
@ -55,18 +55,19 @@ exports.run = async (core, server, socket, data) => {
|
|||
cmd: 'info',
|
||||
type: 'invite',
|
||||
invite: channel,
|
||||
text: `You invited ${data.nick} to ?${channel}`
|
||||
text: `You invited ${data.nick} to ?${channel}`,
|
||||
}, socket);
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('invites-sent');
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['nick'];
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const requiredData = ['nick'];
|
||||
export const info = {
|
||||
name: 'invite',
|
||||
description: 'Generates a unique (more or less) room name and passes it to two clients',
|
||||
usage: `
|
||||
API: { cmd: 'invite', nick: '<target nickname>' }`
|
||||
API: { cmd: 'invite', nick: '<target nickname>' }`,
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
const crypto = require('crypto');
|
||||
|
||||
const hash = (password) => {
|
||||
let sha = crypto.createHash('sha256');
|
||||
const sha = crypto.createHash('sha256');
|
||||
sha.update(password);
|
||||
return sha.digest('base64').substr(0, 6);
|
||||
};
|
||||
|
@ -15,15 +15,15 @@ const verifyNickname = (nick) => /^[a-zA-Z0-9_]{1,24}$/.test(nick);
|
|||
|
||||
// exposed "login" function to allow hooks to verify user join events
|
||||
// returns object containing user info or string if error
|
||||
exports.parseNickname = (core, data) => {
|
||||
let userInfo = {
|
||||
export function parseNickname(core, data) {
|
||||
const userInfo = {
|
||||
nick: '',
|
||||
uType: 'user',
|
||||
trip: null,
|
||||
};
|
||||
|
||||
// seperate nick from password
|
||||
let nickArray = data.nick.split('#', 2);
|
||||
const nickArray = data.nick.split('#', 2);
|
||||
userInfo.nick = nickArray[0].trim();
|
||||
|
||||
if (!verifyNickname(userInfo.nick)) {
|
||||
|
@ -31,90 +31,92 @@ exports.parseNickname = (core, data) => {
|
|||
return 'Nickname must consist of up to 24 letters, numbers, and underscores';
|
||||
}
|
||||
|
||||
let password = nickArray[1];
|
||||
const password = nickArray[1];
|
||||
|
||||
if (hash(password + core.config.tripSalt) === core.config.adminTrip) {
|
||||
userInfo.uType = 'admin';
|
||||
userInfo.trip = 'Admin';
|
||||
} else if (userInfo.nick.toLowerCase() == core.config.adminName.toLowerCase()) { // they've got the main-admin name while not being an admin
|
||||
} else if (userInfo.nick.toLowerCase() === core.config.adminName.toLowerCase()) {
|
||||
// they've got the main-admin name while not being an admin
|
||||
return 'You are not the admin, liar!';
|
||||
} else if (password) {
|
||||
userInfo.trip = hash(password + core.config.tripSalt);
|
||||
}
|
||||
|
||||
// TODO: disallow moderator impersonation
|
||||
for (let mod of core.config.mods) {
|
||||
// for (const mod of core.config.mods) {
|
||||
core.config.mods.forEach((mod) => {
|
||||
if (userInfo.trip === mod.trip) {
|
||||
userInfo.uType = 'mod';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return userInfo;
|
||||
};
|
||||
}
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket, data) {
|
||||
// check for spam
|
||||
if (server.police.frisk(socket.remoteAddress, 3)) {
|
||||
if (server.police.frisk(socket.address, 3)) {
|
||||
return server.reply({
|
||||
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.',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// calling socket already in a channel
|
||||
if (typeof socket.channel !== 'undefined') {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof data.channel !== 'string' || typeof data.nick !== 'string') {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
let channel = data.channel.trim();
|
||||
const channel = data.channel.trim();
|
||||
if (!channel) {
|
||||
// must join a non-blank channel
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
let userInfo = this.parseNickname(core, data);
|
||||
const userInfo = this.parseNickname(core, data);
|
||||
if (typeof userInfo === 'string') {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: userInfo
|
||||
text: userInfo,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// check if the nickname already exists in the channel
|
||||
let userExists = server.findSockets({
|
||||
const userExists = server.findSockets({
|
||||
channel: data.channel,
|
||||
nick: (targetNick) => targetNick.toLowerCase() === userInfo.nick.toLowerCase()
|
||||
nick: (targetNick) => targetNick.toLowerCase() === userInfo.nick.toLowerCase(),
|
||||
});
|
||||
|
||||
if (userExists.length > 0) {
|
||||
// that nickname is already in that channel
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Nickname taken'
|
||||
text: 'Nickname taken',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
userInfo.userHash = server.getSocketHash(socket);
|
||||
|
||||
// prepare to notify channel peers
|
||||
let newPeerList = server.findSockets({ channel: data.channel });
|
||||
let nicks = [];
|
||||
const newPeerList = server.findSockets({ channel: data.channel });
|
||||
const nicks = [];
|
||||
|
||||
let joinAnnouncement = {
|
||||
const joinAnnouncement = {
|
||||
cmd: 'onlineAdd',
|
||||
nick: userInfo.nick,
|
||||
trip: userInfo.trip || 'null',
|
||||
hash: userInfo.userHash
|
||||
hash: userInfo.userHash,
|
||||
};
|
||||
|
||||
// send join announcement and prep online set
|
||||
for (let i = 0, l = newPeerList.length; i < l; i++) {
|
||||
for (let i = 0, l = newPeerList.length; i < l; i += 1) {
|
||||
server.reply(joinAnnouncement, newPeerList[i]);
|
||||
nicks.push(newPeerList[i].nick);
|
||||
}
|
||||
|
@ -131,18 +133,19 @@ exports.run = async (core, server, socket, data) => {
|
|||
// reply with channel peer list
|
||||
server.reply({
|
||||
cmd: 'onlineSet',
|
||||
nicks: nicks
|
||||
nicks,
|
||||
}, socket);
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('users-joined');
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['channel', 'nick'];
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const requiredData = ['channel', 'nick'];
|
||||
export const info = {
|
||||
name: 'join',
|
||||
description: 'Place calling socket into target channel with target nick & broadcast event to channel',
|
||||
usage: `
|
||||
API: { cmd: 'join', nick: '<your nickname>', channel: '<target channel>' }`
|
||||
API: { cmd: 'join', nick: '<your nickname>', channel: '<target channel>' }`,
|
||||
};
|
||||
|
|
|
@ -3,37 +3,38 @@
|
|||
*/
|
||||
|
||||
// module support functions
|
||||
const stripIndents = require('common-tags').stripIndents;
|
||||
const { stripIndents } = require('common-tags');
|
||||
|
||||
const formatTime = (time) => {
|
||||
let seconds = time[0] + time[1] / 1e9;
|
||||
|
||||
let minutes = Math.floor(seconds / 60);
|
||||
seconds = seconds % 60;
|
||||
seconds %= 60;
|
||||
|
||||
let hours = Math.floor(minutes / 60);
|
||||
minutes = minutes % 60;
|
||||
minutes %= 60;
|
||||
|
||||
let days = Math.floor(hours / 24);
|
||||
hours = hours % 24;
|
||||
const days = Math.floor(hours / 24);
|
||||
hours %= 24;
|
||||
|
||||
return `${days.toFixed(0)}d ${hours.toFixed(0)}h ${minutes.toFixed(0)}m ${seconds.toFixed(0)}s`;
|
||||
};
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket) {
|
||||
// gather connection and channel count
|
||||
let ips = {};
|
||||
let channels = {};
|
||||
for (let client of server.clients) {
|
||||
// for (const client of server.clients) {
|
||||
this.clients.forEach((client) => {
|
||||
if (client.channel) {
|
||||
channels[client.channel] = true;
|
||||
ips[client.remoteAddress] = true;
|
||||
ips[client.address] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let uniqueClientCount = Object.keys(ips).length;
|
||||
let uniqueChannels = Object.keys(channels).length;
|
||||
const uniqueClientCount = Object.keys(ips).length;
|
||||
const uniqueChannels = Object.keys(channels).length;
|
||||
|
||||
ips = null;
|
||||
channels = null;
|
||||
|
@ -49,40 +50,39 @@ exports.run = async (core, server, socket, data) => {
|
|||
users-banned: ${(core.stats.get('users-banned') || 0)}
|
||||
users-kicked: ${(core.stats.get('users-kicked') || 0)}
|
||||
stats-requested: ${(core.stats.get('stats-requested') || 0)}
|
||||
server-uptime: ${formatTime(process.hrtime(core.stats.get('start-time')))}`
|
||||
server-uptime: ${formatTime(process.hrtime(core.stats.get('start-time')))}`,
|
||||
}, socket);
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('stats-requested');
|
||||
};
|
||||
}
|
||||
|
||||
// module hook functions
|
||||
exports.initHooks = (server) => {
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.statsCheck, 26);
|
||||
};
|
||||
}
|
||||
|
||||
// hooks chat commands checking for /stats
|
||||
exports.statsCheck = (core, server, socket, payload) => {
|
||||
export function statsCheck(core, server, socket, payload) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/stats')) {
|
||||
this.run(core, server, socket, {
|
||||
cmd: 'morestats'
|
||||
cmd: 'morestats',
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
}
|
||||
|
||||
// module meta
|
||||
exports.info = {
|
||||
export const info = {
|
||||
name: 'morestats',
|
||||
description: 'Sends back current server stats to the calling client',
|
||||
usage: `
|
||||
API: { cmd: 'morestats' }
|
||||
Text: /stats`
|
||||
Text: /stats`,
|
||||
};
|
||||
|
|
|
@ -3,67 +3,67 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket, data) {
|
||||
// check for spam
|
||||
if (server.police.frisk(socket.remoteAddress, 6)) {
|
||||
if (server.police.frisk(socket.address, 6)) {
|
||||
return server.reply({
|
||||
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.',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof data.channel !== 'string') {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data.channel === socket.channel) {
|
||||
// they are trying to rejoin the channel
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// check that the nickname isn't already in target channel
|
||||
const currentNick = socket.nick.toLowerCase();
|
||||
let userExists = server.findSockets({
|
||||
const userExists = server.findSockets({
|
||||
channel: data.channel,
|
||||
nick: (targetNick) => targetNick.toLowerCase() === currentNick
|
||||
nick: (targetNick) => targetNick.toLowerCase() === currentNick,
|
||||
});
|
||||
|
||||
if (userExists.length > 0) {
|
||||
// That nickname is already in that channel
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// broadcast leave notice to peers
|
||||
let peerList = server.findSockets({ channel: socket.channel });
|
||||
const peerList = server.findSockets({ channel: socket.channel });
|
||||
|
||||
if (peerList.length > 1) {
|
||||
for (let i = 0, l = peerList.length; i < l; i++) {
|
||||
for (let i = 0, l = peerList.length; i < l; i += 1) {
|
||||
server.reply({
|
||||
cmd: 'onlineRemove',
|
||||
nick: peerList[i].nick
|
||||
nick: peerList[i].nick,
|
||||
}, socket);
|
||||
|
||||
if (socket.nick !== peerList[i].nick){
|
||||
if (socket.nick !== peerList[i].nick) {
|
||||
server.reply({
|
||||
cmd: 'onlineRemove',
|
||||
nick: socket.nick
|
||||
nick: socket.nick,
|
||||
}, peerList[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// broadcast join notice to new peers
|
||||
let newPeerList = server.findSockets({ channel: data.channel });
|
||||
let moveAnnouncement = {
|
||||
const newPeerList = server.findSockets({ channel: data.channel });
|
||||
const moveAnnouncement = {
|
||||
cmd: 'onlineAdd',
|
||||
nick: socket.nick,
|
||||
trip: socket.trip || 'null',
|
||||
hash: socket.hash
|
||||
hash: socket.hash,
|
||||
};
|
||||
let nicks = [];
|
||||
const nicks = [];
|
||||
|
||||
for (let i = 0, l = newPeerList.length; i < l; i++) {
|
||||
for (let i = 0, l = newPeerList.length; i < l; i += 1) {
|
||||
server.reply(moveAnnouncement, newPeerList[i]);
|
||||
nicks.push(newPeerList[i].nick);
|
||||
}
|
||||
|
@ -73,18 +73,19 @@ exports.run = async (core, server, socket, data) => {
|
|||
// reply with new user list
|
||||
server.reply({
|
||||
cmd: 'onlineSet',
|
||||
nicks: nicks
|
||||
nicks,
|
||||
}, socket);
|
||||
|
||||
// commit change
|
||||
socket.channel = data.channel;
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['channel'];
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const requiredData = ['channel'];
|
||||
export const info = {
|
||||
name: 'move',
|
||||
description: 'This will change your current channel to the new one provided',
|
||||
usage: `
|
||||
API: { cmd: 'move', channel: '<target channel>' }`
|
||||
API: { cmd: 'move', channel: '<target channel>' }`,
|
||||
};
|
||||
|
|
|
@ -3,12 +3,11 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
return;
|
||||
};
|
||||
export async function run() {
|
||||
|
||||
// module meta
|
||||
exports.info = {
|
||||
}
|
||||
|
||||
export const info = {
|
||||
name: 'ping',
|
||||
description: 'This module is only in place to supress error notices legacy sources may get'
|
||||
description: 'This module is only in place to supress error notices legacy sources may get',
|
||||
};
|
||||
|
|
|
@ -3,19 +3,20 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket) {
|
||||
// gather connection and channel count
|
||||
let ips = {};
|
||||
let channels = {};
|
||||
for (let client of server.clients) {
|
||||
// for (const client of server.clients) {
|
||||
this.clients.forEach((client) => {
|
||||
if (client.channel) {
|
||||
channels[client.channel] = true;
|
||||
ips[client.remoteAddress] = true;
|
||||
ips[client.address] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let uniqueClientCount = Object.keys(ips).length;
|
||||
let uniqueChannels = Object.keys(channels).length;
|
||||
const uniqueClientCount = Object.keys(ips).length;
|
||||
const uniqueChannels = Object.keys(channels).length;
|
||||
|
||||
ips = null;
|
||||
channels = null;
|
||||
|
@ -23,17 +24,16 @@ exports.run = async (core, server, socket, data) => {
|
|||
// dispatch info
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `${uniqueClientCount} unique IPs in ${uniqueChannels} channels`
|
||||
text: `${uniqueClientCount} unique IPs in ${uniqueChannels} channels`,
|
||||
}, socket);
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('stats-requested');
|
||||
};
|
||||
}
|
||||
|
||||
// module meta
|
||||
exports.info = {
|
||||
export const info = {
|
||||
name: 'stats',
|
||||
description: 'Sends back legacy server stats to the calling client',
|
||||
usage: `
|
||||
API: { cmd: 'stats' }`
|
||||
API: { cmd: 'stats' }`,
|
||||
};
|
||||
|
|
|
@ -11,36 +11,38 @@ const parseText = (text) => {
|
|||
return false;
|
||||
}
|
||||
|
||||
// strip newlines from beginning and end
|
||||
text = text.replace(/^\s*\n|^\s+$|\n\s*$/g, '');
|
||||
// replace 3+ newlines with just 2 newlines
|
||||
text = text.replace(/\n{3,}/g, "\n\n");
|
||||
let sanitizedText = text;
|
||||
|
||||
return text;
|
||||
// strip newlines from beginning and end
|
||||
sanitizedText = sanitizedText.replace(/^\s*\n|^\s+$|\n\s*$/g, '');
|
||||
// replace 3+ newlines with just 2 newlines
|
||||
sanitizedText = sanitizedText.replace(/\n{3,}/g, '\n\n');
|
||||
|
||||
return sanitizedText;
|
||||
};
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, payload) => {
|
||||
export async function run(core, server, socket, payload) {
|
||||
// check user input
|
||||
let text = parseText(payload.text);
|
||||
const text = parseText(payload.text);
|
||||
|
||||
if (!text) {
|
||||
// lets not send objects or empty text, yea?
|
||||
return server.police.frisk(socket.remoteAddress, 13);
|
||||
return server.police.frisk(socket.address, 13);
|
||||
}
|
||||
|
||||
// check for spam
|
||||
let score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket.remoteAddress, score)) {
|
||||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket.address, score)) {
|
||||
return server.reply({
|
||||
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.',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
let targetNick = payload.nick;
|
||||
const targetNick = payload.nick;
|
||||
if (!verifyNickname(targetNick)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// find target user
|
||||
|
@ -49,18 +51,18 @@ exports.run = async (core, server, socket, payload) => {
|
|||
if (targetClient.length === 0) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in channel'
|
||||
text: 'Could not find user in channel',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
targetClient = targetClient[0];
|
||||
[targetClient] = targetClient;
|
||||
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
type: 'whisper',
|
||||
from: socket.nick,
|
||||
trip: socket.trip || 'null',
|
||||
text: `${socket.nick} whispered: ${text}`
|
||||
text: `${socket.nick} whispered: ${text}`,
|
||||
}, targetClient);
|
||||
|
||||
targetClient.whisperReply = socket.nick;
|
||||
|
@ -68,42 +70,44 @@ exports.run = async (core, server, socket, payload) => {
|
|||
server.reply({
|
||||
cmd: 'info',
|
||||
type: 'whisper',
|
||||
text: `You whispered to @${targetNick}: ${text}`
|
||||
text: `You whispered to @${targetNick}: ${text}`,
|
||||
}, socket);
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// module hook functions
|
||||
exports.initHooks = (server) => {
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.whisperCheck, 20);
|
||||
};
|
||||
}
|
||||
|
||||
// hooks chat commands checking for /whisper
|
||||
exports.whisperCheck = (core, server, socket, payload) => {
|
||||
export function whisperCheck(core, server, socket, payload) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/whisper')) {
|
||||
let input = payload.text.split(' ');
|
||||
const input = payload.text.split(' ');
|
||||
|
||||
// If there is no nickname target parameter
|
||||
if (input[1] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Refer to `/help whisper` for instructions on how to use this command.'
|
||||
text: 'Refer to `/help whisper` for instructions on how to use this command.',
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
let target = input[1].replace(/@/g, '');
|
||||
const target = input[1].replace(/@/g, '');
|
||||
input.splice(0, 2);
|
||||
let whisperText = input.join(' ');
|
||||
const whisperText = input.join(' ');
|
||||
|
||||
this.run(core, server, socket, {
|
||||
cmd: 'whisper',
|
||||
nick: target,
|
||||
text: whisperText
|
||||
text: whisperText,
|
||||
});
|
||||
|
||||
return false;
|
||||
|
@ -113,35 +117,34 @@ exports.whisperCheck = (core, server, socket, payload) => {
|
|||
if (typeof socket.whisperReply === 'undefined') {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Cannot reply to nobody'
|
||||
text: 'Cannot reply to nobody',
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
let input = payload.text.split(' ');
|
||||
const input = payload.text.split(' ');
|
||||
input.splice(0, 1);
|
||||
let whisperText = input.join(' ');
|
||||
const whisperText = input.join(' ');
|
||||
|
||||
this.run(core, server, socket, {
|
||||
cmd: 'whisper',
|
||||
nick: socket.whisperReply,
|
||||
text: whisperText
|
||||
text: whisperText,
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
}
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['nick', 'text'];
|
||||
exports.info = {
|
||||
export const requiredData = ['nick', 'text'];
|
||||
export const info = {
|
||||
name: 'whisper',
|
||||
description: 'Display text on targets screen that only they can see',
|
||||
usage: `
|
||||
API: { cmd: 'whisper', nick: '<target name>', text: '<text to whisper>' }
|
||||
Text: /whisper <target name> <text to whisper>
|
||||
Alt Text: /r <text to whisper, this will auto reply to the last person who whispered to you>`
|
||||
Alt Text: /r <text to whisper, this will auto reply to the last person who whispered to you>`,
|
||||
};
|
||||
|
|
|
@ -4,28 +4,29 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket, data) {
|
||||
if (data.cmdKey !== server.cmdKey) {
|
||||
// internal command attempt by client, increase rate limit chance and ignore
|
||||
return server.police.frisk(socket.remoteAddress, 20);
|
||||
return server.police.frisk(socket.address, 20);
|
||||
}
|
||||
|
||||
// send leave notice to client peers
|
||||
if (socket.channel) {
|
||||
server.broadcast({
|
||||
cmd: 'onlineRemove',
|
||||
nick: socket.nick
|
||||
nick: socket.nick,
|
||||
}, { channel: socket.channel });
|
||||
}
|
||||
|
||||
// commit close just in case
|
||||
socket.terminate();
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['cmdKey'];
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const requiredData = ['cmdKey'];
|
||||
export const info = {
|
||||
name: 'disconnect',
|
||||
usage: 'Internal Use Only',
|
||||
description: 'Internally used to relay `onlineRemove` event to clients'
|
||||
description: 'Internally used to relay `onlineRemove` event to clients',
|
||||
};
|
||||
|
|
|
@ -3,18 +3,19 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
return;
|
||||
};
|
||||
export async function run(core, server, socket, data) {
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
}
|
||||
|
||||
// module hook functions
|
||||
exports.initHooks = (server) => {
|
||||
export function initHooks(server) {
|
||||
// module is only a placeholder
|
||||
//server.registerHook('out', '', this.);
|
||||
};
|
||||
// server.registerHook('out', '', this.);
|
||||
}
|
||||
|
||||
// module meta
|
||||
exports.info = {
|
||||
export const info = {
|
||||
name: 'legacylayer',
|
||||
description: 'This module adjusts outgoing data, making it compatible with legacy clients'
|
||||
description: 'This module adjusts outgoing data, making it compatible with legacy clients',
|
||||
};
|
||||
|
|
|
@ -3,20 +3,21 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket, data) {
|
||||
if (data.cmdKey !== server.cmdKey) {
|
||||
// internal command attempt by client, increase rate limit chance and ignore
|
||||
return server.police.frisk(socket.remoteAddress, 20);
|
||||
return server.police.frisk(socket.address, 20);
|
||||
}
|
||||
|
||||
// send warning to target socket
|
||||
server.reply({ cmd: 'warn', text: data.text }, socket);
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['cmdKey', 'text'];
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const requiredData = ['cmdKey', 'text'];
|
||||
export const info = {
|
||||
name: 'socketreply',
|
||||
usage: 'Internal Use Only',
|
||||
description: 'Internally used to relay warnings to clients'
|
||||
description: 'Internally used to relay warnings to clients',
|
||||
};
|
||||
|
|
|
@ -3,53 +3,53 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(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);
|
||||
return server.police.frisk(socket.address, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof data.nick !== 'string') {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// find target user
|
||||
let targetNick = data.nick;
|
||||
const targetNick = data.nick;
|
||||
let badClient = server.findSockets({ channel: socket.channel, nick: targetNick });
|
||||
|
||||
if (badClient.length === 0) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in channel'
|
||||
text: 'Could not find user in channel',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
badClient = badClient[0];
|
||||
[badClient] = badClient;
|
||||
|
||||
// i guess banning mods or admins isn't the best idea?
|
||||
if (badClient.uType !== 'user') {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Cannot ban other mods, how rude'
|
||||
text: 'Cannot ban other mods, how rude',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// commit arrest record
|
||||
server.police.arrest(badClient.remoteAddress, badClient.hash);
|
||||
server.police.arrest(badClient.address, badClient.hash);
|
||||
|
||||
console.log(`${socket.nick} [${socket.trip}] banned ${targetNick} in ${socket.channel}`);
|
||||
|
||||
// notify normal users
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Banned ${targetNick}`
|
||||
text: `Banned ${targetNick}`,
|
||||
}, { channel: socket.channel, uType: 'user' });
|
||||
|
||||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick} banned ${targetNick} in ${socket.channel}, userhash: ${badClient.hash}`
|
||||
text: `${socket.nick} banned ${targetNick} in ${socket.channel}, userhash: ${badClient.hash}`,
|
||||
}, { uType: 'mod' });
|
||||
|
||||
// force connection closed
|
||||
|
@ -57,13 +57,14 @@ exports.run = async (core, server, socket, data) => {
|
|||
|
||||
// stats are fun
|
||||
core.stats.increment('users-banned');
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['nick'];
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const requiredData = ['nick'];
|
||||
export const info = {
|
||||
name: 'ban',
|
||||
description: 'Disconnects the target nickname in the same channel as calling socket & adds to ratelimiter',
|
||||
usage: `
|
||||
API: { cmd: 'ban', nick: '<target nickname>' }`
|
||||
API: { cmd: 'ban', nick: '<target nickname>' }`,
|
||||
};
|
||||
|
|
|
@ -4,22 +4,22 @@
|
|||
*/
|
||||
|
||||
// module constructor
|
||||
exports.init = (core) => {
|
||||
export function init(core) {
|
||||
if (typeof core.muzzledHashes === 'undefined') {
|
||||
core.muzzledHashes = {};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(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);
|
||||
return server.police.frisk(socket.address, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof data.nick !== 'string') {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// find target user
|
||||
|
@ -28,56 +28,58 @@ exports.run = async (core, server, socket, data) => {
|
|||
if (badClient.length === 0) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in channel'
|
||||
text: 'Could not find user in channel',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
badClient = badClient[0];
|
||||
[badClient] = badClient;
|
||||
|
||||
// likely dont need this, muting mods and admins is fine
|
||||
if (badClient.uType !== 'user') {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'This trick wont work on mods and admin'
|
||||
text: 'This trick wont work on mods and admin',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// store hash in mute list
|
||||
let record = core.muzzledHashes[badClient.hash] = {
|
||||
dumb: true
|
||||
}
|
||||
const record = core.muzzledHashes[badClient.hash] = {
|
||||
dumb: true,
|
||||
};
|
||||
|
||||
// store allies if needed
|
||||
if(data.allies && Array.isArray(data.allies)){
|
||||
record.allies = data.allies;
|
||||
if (data.allies && Array.isArray(data.allies)) {
|
||||
record.allies = data.allies;
|
||||
}
|
||||
|
||||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick} muzzled ${data.nick} in ${socket.channel}, userhash: ${badClient.hash}`
|
||||
text: `${socket.nick} muzzled ${data.nick} in ${socket.channel}, userhash: ${badClient.hash}`,
|
||||
}, { uType: 'mod' });
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// module hook functions
|
||||
exports.initHooks = (server) => {
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.chatCheck, 25);
|
||||
server.registerHook('in', 'invite', this.inviteCheck, 25);
|
||||
// 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) => {
|
||||
export function chatCheck(core, server, socket, payload) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(core.muzzledHashes[socket.hash]){
|
||||
if (core.muzzledHashes[socket.hash]) {
|
||||
// build fake chat payload
|
||||
mutedPayload = {
|
||||
const mutedPayload = {
|
||||
cmd: 'chat',
|
||||
nick: socket.nick,
|
||||
text: payload.text
|
||||
text: payload.text,
|
||||
};
|
||||
|
||||
if (socket.trip) {
|
||||
|
@ -85,50 +87,59 @@ exports.chatCheck = (core, server, socket, payload) => {
|
|||
}
|
||||
|
||||
// broadcast to any duplicate connections in channel
|
||||
server.broadcast( mutedPayload, { channel: socket.channel, hash: socket.hash });
|
||||
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 });
|
||||
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);
|
||||
/**
|
||||
* Blanket "spam" protection.
|
||||
* May expose the ratelimiting lines from `chat` and use that
|
||||
* @todo one day #lazydev
|
||||
*/
|
||||
server.police.frisk(socket.address, 9);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
}
|
||||
|
||||
// shadow-prevent all invites from muzzled users
|
||||
exports.inviteCheck = (core, server, socket, payload) => {
|
||||
export function inviteCheck(core, server, socket, payload) {
|
||||
if (typeof payload.nick !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(core.muzzledHashes[socket.hash]){
|
||||
if (core.muzzledHashes[socket.hash]) {
|
||||
// generate common channel
|
||||
let channel = Math.random().toString(36).substr(2, 8);
|
||||
const channel = Math.random().toString(36).substr(2, 8);
|
||||
|
||||
// send fake reply
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `You invited ${payload.nick} to ?${channel}`
|
||||
text: `You invited ${payload.nick} to ?${channel}`,
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
}
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['nick'];
|
||||
exports.info = {
|
||||
export const requiredData = ['nick'];
|
||||
export const info = {
|
||||
name: 'dumb',
|
||||
description: 'Globally shadow mute a connection. Optional allies array will see muted messages.',
|
||||
usage: `
|
||||
API: { cmd: 'dumb', nick: '<target nick>', allies: ['<optional nick array>', ...] }`
|
||||
API: { cmd: 'dumb', nick: '<target nick>', allies: ['<optional nick array>', ...] }`,
|
||||
};
|
||||
exports.info.aliases = ['muzzle', 'mute'];
|
||||
info.aliases = ['muzzle', 'mute'];
|
||||
|
|
|
@ -3,37 +3,37 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(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);
|
||||
return server.police.frisk(socket.address, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof data.nick !== 'string') {
|
||||
if (typeof data.nick !== 'object' && !Array.isArray(data.nick)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// find target user(s)
|
||||
let badClients = server.findSockets({ channel: socket.channel, nick: data.nick });
|
||||
const badClients = server.findSockets({ channel: socket.channel, nick: data.nick });
|
||||
|
||||
if (badClients.length === 0) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user(s) in channel'
|
||||
text: 'Could not find user(s) in channel',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// check if found targets are kickable, commit kick
|
||||
let newChannel = '';
|
||||
let kicked = [];
|
||||
for (let i = 0, j = badClients.length; i < j; i++) {
|
||||
const kicked = [];
|
||||
for (let i = 0, j = badClients.length; i < j; i += 1) {
|
||||
if (badClients[i].uType !== 'user') {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Cannot kick other mods, how rude'
|
||||
text: 'Cannot kick other mods, how rude',
|
||||
}, socket);
|
||||
} else {
|
||||
newChannel = Math.random().toString(36).substr(2, 8);
|
||||
|
@ -42,7 +42,7 @@ exports.run = async (core, server, socket, data) => {
|
|||
// inform mods with where they were sent
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${badClients[i].nick} was banished to ?${newChannel}`
|
||||
text: `${badClients[i].nick} was banished to ?${newChannel}`,
|
||||
}, { channel: socket.channel, uType: 'mod' });
|
||||
|
||||
kicked.push(badClients[i].nick);
|
||||
|
@ -51,32 +51,33 @@ exports.run = async (core, server, socket, data) => {
|
|||
}
|
||||
|
||||
if (kicked.length === 0) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// broadcast client leave event
|
||||
for (let i = 0, j = kicked.length; i < j; i++) {
|
||||
for (let i = 0, j = kicked.length; i < j; i += 1) {
|
||||
server.broadcast({
|
||||
cmd: 'onlineRemove',
|
||||
nick: kicked[i]
|
||||
nick: kicked[i],
|
||||
}, { channel: socket.channel });
|
||||
}
|
||||
|
||||
// publicly broadcast kick event
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Kicked ${kicked.join(', ')}`
|
||||
text: `Kicked ${kicked.join(', ')}`,
|
||||
}, { channel: socket.channel, uType: 'user' });
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('users-kicked', kicked.length);
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['nick'];
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const requiredData = ['nick'];
|
||||
export const info = {
|
||||
name: 'kick',
|
||||
description: 'Silently forces target client(s) into another channel. `nick` may be string or array of strings',
|
||||
usage: `
|
||||
API: { cmd: 'kick', nick: '<target nick>' }`
|
||||
API: { cmd: 'kick', nick: '<target nick>' }`,
|
||||
};
|
||||
|
|
|
@ -3,79 +3,79 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(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);
|
||||
return server.police.frisk(socket.address, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof data.nick !== 'string' || typeof data.channel !== 'string') {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data.channel === socket.channel) {
|
||||
// moving them into the same channel? y u do this?
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
let badClients = server.findSockets({ channel: socket.channel, nick: data.nick });
|
||||
const 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'
|
||||
text: 'Could not find user in channel',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
let badClient = badClients[0];
|
||||
const badClient = badClients[0];
|
||||
|
||||
if (badClient.uType !== 'user') {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Cannot move other mods, how rude'
|
||||
text: 'Cannot move other mods, how rude',
|
||||
}, socket);
|
||||
}
|
||||
|
||||
const currentNick = badClient.nick.toLowerCase();
|
||||
let userExists = server.findSockets({
|
||||
const userExists = server.findSockets({
|
||||
channel: data.channel,
|
||||
nick: (targetNick) => targetNick.toLowerCase() === currentNick
|
||||
nick: (targetNick) => targetNick.toLowerCase() === currentNick,
|
||||
});
|
||||
|
||||
if (userExists.length > 0) {
|
||||
// That nickname is already in that channel
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
let peerList = server.findSockets({ channel: socket.channel });
|
||||
const peerList = server.findSockets({ channel: socket.channel });
|
||||
|
||||
if (peerList.length > 1) {
|
||||
for (let i = 0, l = peerList.length; i < l; i++) {
|
||||
for (let i = 0, l = peerList.length; i < l; i += 1) {
|
||||
server.reply({
|
||||
cmd: 'onlineRemove',
|
||||
nick: peerList[i].nick
|
||||
nick: peerList[i].nick,
|
||||
}, badClient);
|
||||
|
||||
if (badClient.nick !== peerList[i].nick){
|
||||
if (badClient.nick !== peerList[i].nick) {
|
||||
server.reply({
|
||||
cmd: 'onlineRemove',
|
||||
nick: badClient.nick
|
||||
nick: badClient.nick,
|
||||
}, peerList[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let newPeerList = server.findSockets({ channel: data.channel });
|
||||
let moveAnnouncement = {
|
||||
const newPeerList = server.findSockets({ channel: data.channel });
|
||||
const moveAnnouncement = {
|
||||
cmd: 'onlineAdd',
|
||||
nick: badClient.nick,
|
||||
trip: badClient.trip || 'null',
|
||||
hash: server.getSocketHash(badClient)
|
||||
hash: server.getSocketHash(badClient),
|
||||
};
|
||||
let nicks = [];
|
||||
const nicks = [];
|
||||
|
||||
for (let i = 0, l = newPeerList.length; i < l; i++) {
|
||||
for (let i = 0, l = newPeerList.length; i < l; i += 1) {
|
||||
server.reply(moveAnnouncement, newPeerList[i]);
|
||||
nicks.push(newPeerList[i].nick);
|
||||
}
|
||||
|
@ -84,22 +84,23 @@ exports.run = async (core, server, socket, data) => {
|
|||
|
||||
server.reply({
|
||||
cmd: 'onlineSet',
|
||||
nicks: nicks
|
||||
nicks,
|
||||
}, badClient);
|
||||
|
||||
badClient.channel = data.channel;
|
||||
|
||||
server.broadcast( {
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${badClient.nick} was moved into ?${data.channel}`
|
||||
text: `${badClient.nick} was moved into ?${data.channel}`,
|
||||
}, { channel: data.channel });
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.requiredData = ['nick', 'channel'];
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const requiredData = ['nick', 'channel'];
|
||||
export const info = {
|
||||
name: 'moveuser',
|
||||
description: 'This will move the target user nick into another channel',
|
||||
usage: `
|
||||
API: { cmd: 'moveuser', nick: '<target nick>', channel: '<new channel>' }`
|
||||
API: { cmd: 'moveuser', nick: '<target nick>', channel: '<new channel>' }`,
|
||||
};
|
||||
|
|
|
@ -3,32 +3,32 @@
|
|||
* Author: simple
|
||||
*/
|
||||
|
||||
// module constructor
|
||||
exports.init = (core) => {
|
||||
if (typeof core.muzzledHashes === 'undefined') {
|
||||
core.muzzledHashes = {};
|
||||
}
|
||||
};
|
||||
// module constructor
|
||||
export function init(core) {
|
||||
if (typeof core.muzzledHashes === 'undefined') {
|
||||
core.muzzledHashes = {};
|
||||
}
|
||||
}
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(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);
|
||||
return server.police.frisk(socket.address, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof data.ip !== 'string' && typeof data.hash !== 'string') {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: "hash:'targethash' or ip:'1.2.3.4' is required"
|
||||
text: "hash:'targethash' or ip:'1.2.3.4' is required",
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// find target & remove mute status
|
||||
let target;
|
||||
if (typeof data.ip === 'string') {
|
||||
target = getSocketHash(data.ip);
|
||||
target = server.getSocketHash(data.ip);
|
||||
} else {
|
||||
target = data.hash;
|
||||
}
|
||||
|
@ -38,15 +38,16 @@ exports.run = async (core, server, socket, data) => {
|
|||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick} unmuzzled : ${target}`
|
||||
text: `${socket.nick} unmuzzled : ${target}`,
|
||||
}, { uType: 'mod' });
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const info = {
|
||||
name: 'speak',
|
||||
description: 'Pardon a dumb user to be able to speak again',
|
||||
usage: `
|
||||
API: { cmd: 'speak', ip/hash: '<target ip or hash' }`
|
||||
API: { cmd: 'speak', ip/hash: '<target ip or hash' }`,
|
||||
};
|
||||
exports.info.aliases = ['unmuzzle', 'unmute'];
|
||||
info.aliases = ['unmuzzle', 'unmute'];
|
||||
|
|
|
@ -3,22 +3,23 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(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);
|
||||
return server.police.frisk(socket.address, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof data.ip !== 'string' && typeof data.hash !== 'string') {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: "hash:'targethash' or ip:'1.2.3.4' is required"
|
||||
text: "hash:'targethash' or ip:'1.2.3.4' is required",
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// find target
|
||||
let mode, target;
|
||||
let mode; let
|
||||
target;
|
||||
if (typeof data.ip === 'string') {
|
||||
mode = 'ip';
|
||||
target = data.ip;
|
||||
|
@ -39,23 +40,24 @@ exports.run = async (core, server, socket, data) => {
|
|||
// reply with success
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `Unbanned ${target}`
|
||||
text: `Unbanned ${target}`,
|
||||
}, socket);
|
||||
|
||||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick} unbanned: ${target}`
|
||||
text: `${socket.nick} unbanned: ${target}`,
|
||||
}, { uType: 'mod' });
|
||||
|
||||
// stats are fun
|
||||
core.stats.decrement('users-banned');
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const info = {
|
||||
name: 'unban',
|
||||
description: 'Removes target ip from the ratelimiter',
|
||||
usage: `
|
||||
API: { cmd: 'unban', ip/hash: '<target ip or hash>' }`
|
||||
API: { cmd: 'unban', ip/hash: '<target ip or hash>' }`,
|
||||
};
|
||||
|
|
|
@ -3,34 +3,35 @@
|
|||
*/
|
||||
|
||||
// module main
|
||||
exports.run = async (core, server, socket, data) => {
|
||||
export async function run(core, server, socket) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (socket.uType === 'user') {
|
||||
return server.police.frisk(socket.remoteAddress, 10);
|
||||
return server.police.frisk(socket.address, 10);
|
||||
}
|
||||
|
||||
// remove arrest records
|
||||
server.police.records = {};
|
||||
server.police.clear();
|
||||
|
||||
console.log(`${socket.nick} [${socket.trip}] unbanned all`);
|
||||
|
||||
// reply with success
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `Unbanned all ip addresses`
|
||||
text: 'Unbanned all ip addresses',
|
||||
}, socket);
|
||||
|
||||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick} unbanned all ip addresses`
|
||||
text: `${socket.nick} unbanned all ip addresses`,
|
||||
}, { uType: 'mod' });
|
||||
};
|
||||
|
||||
// module meta
|
||||
exports.info = {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const info = {
|
||||
name: 'unbanall',
|
||||
description: 'Clears all banned ip addresses',
|
||||
usage: `
|
||||
API: { cmd: 'unbanall' }`
|
||||
API: { cmd: 'unbanall' }`,
|
||||
};
|
||||
|
|
|
@ -1,40 +1,36 @@
|
|||
import {
|
||||
start as _start,
|
||||
get,
|
||||
} from 'prompt';
|
||||
|
||||
/**
|
||||
* Server setup wizard, quick server setup and all that jazz. . .
|
||||
*
|
||||
* Version: v2.0.0
|
||||
* Developer: Marzavec ( https://github.com/marzavec )
|
||||
* License: WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @version v2.0.0
|
||||
* @license WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*/
|
||||
|
||||
const fse = require('fs-extra');
|
||||
const prompt = require('prompt');
|
||||
const path = require('path');
|
||||
|
||||
class SetupWizard {
|
||||
/**
|
||||
* Create a `SetupWizard` instance for initializing the server's config.json
|
||||
*
|
||||
* @param {Object} serverConfig reference to the server config class
|
||||
*/
|
||||
constructor (serverConfig) {
|
||||
constructor(serverConfig) {
|
||||
this.serverConfig = serverConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll a d20 and begin the wizarding process
|
||||
*
|
||||
*/
|
||||
async start () {
|
||||
async start() {
|
||||
// load the current config to use as defaults, if available
|
||||
let currentConfig = await this.serverConfig.load() || {};
|
||||
const currentConfig = await this.serverConfig.load() || {};
|
||||
|
||||
// auto generate the salt if not currrently created
|
||||
currentConfig.tripSalt = currentConfig.tripSalt ||
|
||||
[...Array(Math.floor(Math.random()*1024)+1024)].map(i=>(~~(Math.random()*36)).toString(36)).join('');
|
||||
currentConfig.tripSalt = currentConfig.tripSalt
|
||||
|| [...Array(Math.floor(Math.random() * 1024) + 1024)].map(() => (~~(Math.random() * 36)).toString(36)).join('');
|
||||
|
||||
// load the setup questions & set their defaults
|
||||
let questions = require('../setupSchema/Questions.js');
|
||||
const questions = require('../setupSchema/Questions.js');
|
||||
questions.properties = this.setQuestionDefaults(questions.properties, currentConfig);
|
||||
|
||||
// force password re-entry
|
||||
|
@ -45,19 +41,18 @@ class SetupWizard {
|
|||
require('../setupSchema/Banner.js');
|
||||
|
||||
// let's start playing 20 questions
|
||||
prompt.start();
|
||||
prompt.get(questions, (err, result) => this.finalize(err, result));
|
||||
_start();
|
||||
get(questions, (err, result) => this.finalize(err, result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the currently loaded config with the stock questions, adds a default
|
||||
* and required option to the question
|
||||
*
|
||||
* @param {Object} questions the set of questions from /setupSchema
|
||||
* @param {Object} currentConfig the current server options
|
||||
*/
|
||||
setQuestionDefaults (questions, currentConfig) {
|
||||
Object.keys(questions).forEach(qName => {
|
||||
setQuestionDefaults(questions, currentConfig) {
|
||||
Object.keys(questions).forEach((qName) => {
|
||||
if (typeof currentConfig[qName] !== 'undefined') {
|
||||
questions[qName].default = currentConfig[qName];
|
||||
questions[qName].required = false;
|
||||
|
@ -76,7 +71,7 @@ class SetupWizard {
|
|||
* @param {Object} err any errors generated by Prompt
|
||||
* @param {Object} result the answers / new config setup
|
||||
*/
|
||||
async finalize (err, result) {
|
||||
async finalize(err, result) {
|
||||
// output errors and die if needed
|
||||
if (err) {
|
||||
console.error(err);
|
||||
|
@ -87,7 +82,7 @@ class SetupWizard {
|
|||
if (typeof result.mods === 'undefined') {
|
||||
result.mods = [];
|
||||
}
|
||||
|
||||
|
||||
// If we should log errors with the err stack when they occur.
|
||||
// See: CommandManager.js
|
||||
if (typeof result.logErrDetailed === 'undefined') {
|
||||
|
@ -110,4 +105,4 @@ class SetupWizard {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = SetupWizard;
|
||||
export default SetupWizard;
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
/**
|
||||
* Server configuration script, to (re)configure server options
|
||||
*
|
||||
* Version: v2.0.0
|
||||
* Developer: Marzavec ( https://github.com/marzavec )
|
||||
* License: WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @version v2.0.0
|
||||
* @license WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// import required classes
|
||||
const path = require('path');
|
||||
const ConfigManager = require('../serverLib/ConfigManager');
|
||||
const SetupWizard = require('./configLib/SetupWizard');
|
||||
import { join } from 'path';
|
||||
import ConfigManager from '../serverLib/ConfigManager';
|
||||
import SetupWizard from './configLib/SetupWizard';
|
||||
|
||||
// import and initialize configManager & dependencies
|
||||
const serverConfig = new ConfigManager(path.join(__dirname, '../..'));
|
||||
const serverConfig = new ConfigManager(join(__dirname, '../..'));
|
||||
const setup = new SetupWizard(serverConfig);
|
||||
|
||||
setup.start();
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
*
|
||||
*/
|
||||
|
||||
const stripIndents = require('common-tags').stripIndents;
|
||||
const chalk = require('chalk');
|
||||
import { stripIndents } from 'common-tags';
|
||||
import chalk from 'chalk';
|
||||
|
||||
// gotta have that sexy console
|
||||
console.log(stripIndents`
|
||||
|
@ -20,9 +20,9 @@ console.log(stripIndents`
|
|||
${chalk.white('Note:')} ${chalk.green('npm/yarn run config')} will re-run this utility.
|
||||
|
||||
You will now be asked for the following:
|
||||
- ${chalk.magenta(' Salt')}, the salt for username trip
|
||||
- ${chalk.magenta('Admin Name')}, the initial admin username
|
||||
- ${chalk.magenta('Admin Pass')}, the initial admin password
|
||||
- ${chalk.magenta(' Port')}, the port for the websocket
|
||||
- ${chalk.magenta(' Salt')}, the salt for username trip
|
||||
- ${chalk.magenta('Admin Name')}, the initial admin username
|
||||
- ${chalk.magenta('Admin Pass')}, the initial admin password
|
||||
- ${chalk.magenta(' Port')}, the port for the websocket
|
||||
\u200b
|
||||
`);
|
||||
|
|
|
@ -15,10 +15,10 @@ const Questions = {
|
|||
type: 'string',
|
||||
hidden: true,
|
||||
replace: '*',
|
||||
before: value => {
|
||||
before: (value) => {
|
||||
salt = value;
|
||||
return value;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
adminName: {
|
||||
|
@ -26,7 +26,7 @@ const Questions = {
|
|||
pattern: /^"?[a-zA-Z0-9_]+"?$/,
|
||||
type: 'string',
|
||||
message: 'Nicks can only contain letters, numbers and underscores',
|
||||
before: value => value.replace(/"/g, '')
|
||||
before: (value) => value.replace(/"/g, ''),
|
||||
},
|
||||
|
||||
adminTrip: {
|
||||
|
@ -35,21 +35,21 @@ const Questions = {
|
|||
replace: '*',
|
||||
description: 'Admin Password',
|
||||
message: 'You must enter or re-enter a password',
|
||||
before: value => {
|
||||
before: (value) => {
|
||||
const crypto = require('crypto');
|
||||
let sha = crypto.createHash('sha256');
|
||||
const sha = crypto.createHash('sha256');
|
||||
sha.update(value + salt);
|
||||
return sha.digest('base64').substr(0, 6);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
websocketPort: {
|
||||
type: 'integer',
|
||||
message: 'The port may only be a number!',
|
||||
description: 'Websocket Port',
|
||||
default: '6060'
|
||||
}
|
||||
}
|
||||
}
|
||||
default: '6060',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = Questions;
|
||||
|
|
|
@ -1,47 +1,71 @@
|
|||
/**
|
||||
* Commands / protocol manager- loads, validates and handles command execution
|
||||
*
|
||||
* Version: v2.0.0
|
||||
* Developer: Marzavec ( https://github.com/marzavec )
|
||||
* License: WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const didYouMean = require('didyoumean2').default;
|
||||
import {
|
||||
basename,
|
||||
join,
|
||||
sep,
|
||||
dirname,
|
||||
relative,
|
||||
} from 'path';
|
||||
import didYouMean from 'didyoumean2';
|
||||
|
||||
// default command modules path
|
||||
const CmdDir = 'src/commands';
|
||||
|
||||
/**
|
||||
* Commands / protocol manager- loads, validates and handles command execution
|
||||
* @property {Array} commands - Array of currently loaded command modules
|
||||
* @property {Array} categories - Array of command modules categories
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @version v2.0.0
|
||||
* @license WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*/
|
||||
class CommandManager {
|
||||
/**
|
||||
* Create a `CommandManager` instance for handling commands/protocol
|
||||
*
|
||||
* @param {Object} core Reference to the global core object
|
||||
*/
|
||||
constructor (core) {
|
||||
constructor(core) {
|
||||
/**
|
||||
* Stored reference to the core
|
||||
* @type {CoreApp}
|
||||
*/
|
||||
this.core = core;
|
||||
|
||||
/**
|
||||
* Command module storage
|
||||
* @type {Array}
|
||||
*/
|
||||
this.commands = [];
|
||||
|
||||
/**
|
||||
* Command module category names (based off directory or module meta)
|
||||
* @type {Array}
|
||||
*/
|
||||
this.categories = [];
|
||||
if (!this.core.config.hasOwnProperty('logErrDetailed')) {
|
||||
|
||||
/**
|
||||
* Full path to config.json file
|
||||
* @type {String}
|
||||
*/
|
||||
if (typeof this.core.config.logErrDetailed === 'undefined') {
|
||||
this.core.config.logErrDetailed = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* (Re)initializes name spaces for commands and starts load routine
|
||||
*
|
||||
* @public
|
||||
* @return {String} Module errors or empty if none
|
||||
*/
|
||||
loadCommands () {
|
||||
loadCommands() {
|
||||
this.commands = [];
|
||||
this.categories = [];
|
||||
|
||||
const commandImports = this.core.dynamicImports.getImport(CmdDir);
|
||||
let cmdErrors = '';
|
||||
Object.keys(commandImports).forEach(file => {
|
||||
let command = commandImports[file];
|
||||
let name = path.basename(file);
|
||||
Object.keys(commandImports).forEach((file) => {
|
||||
const command = commandImports[file];
|
||||
const name = basename(file);
|
||||
cmdErrors += this.validateAndLoad(command, file, name);
|
||||
});
|
||||
|
||||
|
@ -50,29 +74,28 @@ class CommandManager {
|
|||
|
||||
/**
|
||||
* Checks the module after having been `require()`ed in and reports errors
|
||||
*
|
||||
* @param {Object} command reference to the newly loaded object
|
||||
* @param {String} file file path to the module
|
||||
* @param {String} name command (`cmd`) name
|
||||
*
|
||||
* @private
|
||||
* @return {String} Module errors or empty if none
|
||||
*/
|
||||
validateAndLoad (command, file, name) {
|
||||
let error = this.validateCommand(command);
|
||||
validateAndLoad(command, file, name) {
|
||||
const error = this.validateCommand(command);
|
||||
|
||||
if (error) {
|
||||
let errText = `Failed to load '${name}': ${error}`;
|
||||
const errText = `Failed to load command module '${name}': ${error}`;
|
||||
console.log(errText);
|
||||
return errText;
|
||||
}
|
||||
|
||||
if (!command.category) {
|
||||
let base = path.join(this.core.dynamicImports.base, 'commands');
|
||||
const base = join(this.core.dynamicImports.base, 'commands');
|
||||
|
||||
let category = 'Uncategorized';
|
||||
if (file.indexOf(path.sep) > -1) {
|
||||
category = path.dirname(path.relative(base, file))
|
||||
.replace(new RegExp(path.sep.replace('\\', '\\\\'), 'g'), '/');
|
||||
if (file.indexOf(sep) > -1) {
|
||||
category = dirname(relative(base, file))
|
||||
.replace(new RegExp(sep.replace('\\', '\\\\'), 'g'), '/');
|
||||
}
|
||||
|
||||
command.info.category = category;
|
||||
|
@ -86,7 +109,7 @@ class CommandManager {
|
|||
try {
|
||||
command.init(this.core);
|
||||
} catch (err) {
|
||||
let errText = `Failed to initialize '${name}': ${err}`;
|
||||
const errText = `Failed to initialize '${name}': ${err}`;
|
||||
console.log(errText);
|
||||
return errText;
|
||||
}
|
||||
|
@ -99,121 +122,109 @@ class CommandManager {
|
|||
|
||||
/**
|
||||
* Checks the module after having been `require()`ed in and reports errors
|
||||
*
|
||||
* @param {Object} object reference to the newly loaded object
|
||||
*
|
||||
* @private
|
||||
* @return {String} Module errors or null if none
|
||||
*/
|
||||
validateCommand (object) {
|
||||
if (typeof object !== 'object')
|
||||
return 'command setup is invalid';
|
||||
|
||||
if (typeof object.run !== 'function')
|
||||
return 'run function is missing';
|
||||
|
||||
if (typeof object.info !== 'object')
|
||||
return 'info object is missing';
|
||||
|
||||
if (typeof object.info.name !== 'string')
|
||||
return 'info object is missing a valid name field';
|
||||
validateCommand(object) {
|
||||
if (typeof object !== 'object') { return 'command setup is invalid'; }
|
||||
if (typeof object.run !== 'function') { return 'run function is missing'; }
|
||||
if (typeof object.info !== 'object') { return 'info object is missing'; }
|
||||
if (typeof object.info.name !== 'string') { return 'info object is missing a valid name field'; }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls all command names from a passed `category`
|
||||
*
|
||||
* @param {String} category [Optional] filter return results by this category
|
||||
*
|
||||
* @public
|
||||
* @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()
|
||||
);
|
||||
(c) => c.info.category.toLowerCase() === category.toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls all category names
|
||||
*
|
||||
* @return {Array} Array of sub directories under CmdDir
|
||||
* All category names
|
||||
* @public
|
||||
* @readonly
|
||||
* @return {Array} Array of command category names
|
||||
*/
|
||||
get categoriesList () {
|
||||
get categoriesList() {
|
||||
return this.categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls command by name or alia(s)
|
||||
*
|
||||
* Pulls command by name or alias
|
||||
* @param {String} name name or alias of command
|
||||
*
|
||||
* @public
|
||||
* @return {Object} Target command module object
|
||||
*/
|
||||
get (name) {
|
||||
get(name) {
|
||||
return this.findBy('name', name)
|
||||
|| this.commands.find(
|
||||
command => command.info.aliases instanceof Array &&
|
||||
command.info.aliases.indexOf(name) > -1
|
||||
(command) => command.info.aliases instanceof Array
|
||||
&& command.info.aliases.indexOf(name) > -1,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls command by arbitrary search of the `module.info` attribute
|
||||
*
|
||||
* @param {String} key name or alias of command
|
||||
* @param {String} value name or alias of command
|
||||
*
|
||||
* @public
|
||||
* @return {Object} Target command module object
|
||||
*/
|
||||
findBy (key, value) {
|
||||
return this.commands.find(c => c.info[key] === value);
|
||||
findBy(key, value) {
|
||||
return this.commands.find((c) => c.info[key] === value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs `initHooks` function on any modules that utilize the event
|
||||
*
|
||||
* @private
|
||||
* @param {Object} server main server object
|
||||
*/
|
||||
initCommandHooks (server) {
|
||||
this.commands.filter(c => typeof c.initHooks !== 'undefined').forEach(
|
||||
c => c.initHooks(server)
|
||||
);
|
||||
initCommandHooks(server) {
|
||||
this.commands.filter((c) => typeof c.initHooks !== 'undefined').forEach(
|
||||
(c) => c.initHooks(server),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and executes the requested command, or fails with semi-intelligent error
|
||||
*
|
||||
* @param {Object} server main server reference
|
||||
* @param {Object} socket calling socket reference
|
||||
* @param {Object} data command structure passed by socket (client)
|
||||
*
|
||||
* @public
|
||||
* @return {*} Arbitrary module return data
|
||||
*/
|
||||
handleCommand (server, socket, data) {
|
||||
handleCommand(server, socket, data) {
|
||||
// Try to find command first
|
||||
let command = this.get(data.cmd);
|
||||
const command = this.get(data.cmd);
|
||||
|
||||
if (command) {
|
||||
return this.execute(command, server, socket, data);
|
||||
} else {
|
||||
// Then fail with helpful (sorta) message
|
||||
return this.handleFail(server, socket, data);
|
||||
}
|
||||
|
||||
// Then fail with helpful (sorta) message
|
||||
return this.handleFail(server, socket, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requested command failure handler, attempts to find command and reports back
|
||||
*
|
||||
* @param {Object} server main server reference
|
||||
* @param {Object} socket calling socket reference
|
||||
* @param {Object} data command structure passed by socket (client)
|
||||
*
|
||||
* @private
|
||||
* @return {*} Arbitrary module return data
|
||||
*/
|
||||
handleFail (server, socket, data) {
|
||||
const maybe = didYouMean(data.cmd, this.all().map(c => c.info.name), {
|
||||
handleFail(server, socket, data) {
|
||||
const maybe = didYouMean(data.cmd, this.all().map((c) => c.info.name), {
|
||||
threshold: 5,
|
||||
thresholdType: 'edit-distance'
|
||||
thresholdType: 'edit-distance',
|
||||
});
|
||||
|
||||
if (maybe) {
|
||||
|
@ -221,7 +232,7 @@ class CommandManager {
|
|||
return this.handleCommand(server, socket, {
|
||||
cmd: 'socketreply',
|
||||
cmdKey: server.cmdKey,
|
||||
text: `Command not found, did you mean: \`${maybe}\`?`
|
||||
text: `Command not found, did you mean: \`${maybe}\`?`,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -229,39 +240,37 @@ class CommandManager {
|
|||
return this.handleCommand(server, socket, {
|
||||
cmd: 'socketreply',
|
||||
cmdKey: server.cmdKey,
|
||||
text: 'Unknown command'
|
||||
text: 'Unknown command',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to execute the requested command, fail if err or bad params
|
||||
*
|
||||
* @param {Object} command target command module
|
||||
* @param {Object} server main server reference
|
||||
* @param {Object} socket calling socket reference
|
||||
* @param {Object} data command structure passed by socket (client)
|
||||
*
|
||||
* @private
|
||||
* @return {*} Arbitrary module return data
|
||||
*/
|
||||
async execute (command, server, socket, data) {
|
||||
async execute(command, server, socket, data) {
|
||||
if (typeof command.requiredData !== 'undefined') {
|
||||
let missing = [];
|
||||
for (let i = 0, len = command.requiredData.length; i < len; i++) {
|
||||
if (typeof data[command.requiredData[i]] === 'undefined')
|
||||
missing.push(command.requiredData[i]);
|
||||
const missing = [];
|
||||
for (let i = 0, len = command.requiredData.length; i < len; i += 1) {
|
||||
if (typeof data[command.requiredData[i]] === 'undefined') { missing.push(command.requiredData[i]); }
|
||||
}
|
||||
|
||||
if (missing.length > 0) {
|
||||
console.log(`Failed to execute '${
|
||||
command.info.name
|
||||
}': missing required ${missing.join(', ')}\n\n`);
|
||||
command.info.name
|
||||
}': missing required ${missing.join(', ')}\n\n`);
|
||||
|
||||
this.handleCommand(server, socket, {
|
||||
cmd: 'socketreply',
|
||||
cmdKey: server.cmdKey,
|
||||
text: `Failed to execute '${
|
||||
command.info.name
|
||||
}': missing required ${missing.join(', ')}\n\n`
|
||||
command.info.name
|
||||
}': missing required ${missing.join(', ')}\n\n`,
|
||||
});
|
||||
|
||||
return null;
|
||||
|
@ -271,20 +280,20 @@ class CommandManager {
|
|||
try {
|
||||
return await command.run(this.core, server, socket, data);
|
||||
} catch (err) {
|
||||
let errText = `Failed to execute '${command.info.name}': `;
|
||||
|
||||
const errText = `Failed to execute '${command.info.name}': `;
|
||||
|
||||
// If we have more detail enabled, then we get the trace
|
||||
// if it isn't, or the property doesn't exist, then we'll get only the message
|
||||
if (this.core.config.logErrDetailed === true) {
|
||||
console.log(errText + err.stack);
|
||||
} else {
|
||||
console.log(errText + err.toString())
|
||||
console.log(errText + err.toString());
|
||||
}
|
||||
|
||||
this.handleCommand(server, socket, {
|
||||
cmd: 'socketreply',
|
||||
cmdKey: server.cmdKey,
|
||||
text: errText + err.toString()
|
||||
text: errText + err.toString(),
|
||||
});
|
||||
|
||||
return null;
|
||||
|
@ -292,4 +301,4 @@ class CommandManager {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = CommandManager;
|
||||
export default CommandManager;
|
||||
|
|
|
@ -1,38 +1,47 @@
|
|||
import dateFormat from 'dateformat';
|
||||
import {
|
||||
existsSync,
|
||||
ensureFileSync,
|
||||
readJsonSync,
|
||||
copySync,
|
||||
writeJSONSync,
|
||||
removeSync,
|
||||
} from 'fs-extra';
|
||||
import { resolve } from 'path';
|
||||
|
||||
/**
|
||||
* Server configuration manager, handling loading, creation, parsing and saving
|
||||
* of the main config.json file
|
||||
*
|
||||
* Version: v2.0.0
|
||||
* Developer: Marzavec ( https://github.com/marzavec )
|
||||
* License: WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*
|
||||
* @property {String} base - Base path that all imports are required in from
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @version v2.0.0
|
||||
* @license WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*/
|
||||
const dateFormat = require('dateformat');
|
||||
const fse = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
class ConfigManager {
|
||||
/**
|
||||
* Create a `ConfigManager` instance for managing application settings
|
||||
*
|
||||
* @param {String} basePath executing directory name; __dirname
|
||||
*/
|
||||
constructor (basePath = __dirname) {
|
||||
this.configPath = path.resolve(basePath, 'config/config.json');
|
||||
constructor(basePath = __dirname) {
|
||||
/**
|
||||
* Full path to config.json file
|
||||
* @type {String}
|
||||
*/
|
||||
this.configPath = resolve(basePath, 'config/config.json');
|
||||
|
||||
if (!fse.existsSync(this.configPath)){
|
||||
fse.ensureFileSync(this.configPath);
|
||||
if (!existsSync(this.configPath)) {
|
||||
ensureFileSync(this.configPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads config.json (main server config) into mem
|
||||
*
|
||||
* @return {Object || Boolean} False if the config.json could not be loaded
|
||||
* Loads config.json (main server config) into memory
|
||||
* @public
|
||||
* @return {(JSON|Boolean)} False if the config.json could not be loaded
|
||||
*/
|
||||
async load () {
|
||||
async load() {
|
||||
try {
|
||||
this.config = fse.readJsonSync(this.configPath);
|
||||
this.config = readJsonSync(this.configPath);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
@ -42,12 +51,12 @@ class ConfigManager {
|
|||
|
||||
/**
|
||||
* Creates backup of current config into configPath
|
||||
*
|
||||
* @private
|
||||
* @return {String} Backed up config.json path
|
||||
*/
|
||||
async backup () {
|
||||
backup() {
|
||||
const backupPath = `${this.configPath}.${dateFormat('dd-mm-yy-HH-MM-ss')}.bak`;
|
||||
fse.copySync(this.configPath, backupPath);
|
||||
copySync(this.configPath, backupPath);
|
||||
|
||||
return backupPath;
|
||||
}
|
||||
|
@ -55,18 +64,18 @@ class ConfigManager {
|
|||
/**
|
||||
* First makes a backup of the current `config.json`, then writes current config
|
||||
* to disk
|
||||
*
|
||||
* @public
|
||||
* @return {Boolean} False on failure
|
||||
*/
|
||||
async save () {
|
||||
const backupPath = await this.backup();
|
||||
save() {
|
||||
const backupPath = this.backup();
|
||||
|
||||
try {
|
||||
fse.writeJSONSync(this.configPath, this.config, {
|
||||
// Indent with two spaces
|
||||
writeJSONSync(this.configPath, this.config, {
|
||||
// Indent with two spaces
|
||||
spaces: 2,
|
||||
});
|
||||
fse.removeSync(backupPath);
|
||||
removeSync(backupPath);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
|
@ -78,18 +87,17 @@ class ConfigManager {
|
|||
|
||||
/**
|
||||
* Updates current config[`key`] with `value` then writes changes to disk
|
||||
*
|
||||
* @param {*} key arbitrary configuration key
|
||||
* @param {*} value new value to change `key` to
|
||||
*
|
||||
* @public
|
||||
* @return {Boolean} False on failure
|
||||
*/
|
||||
async set (key, value) {
|
||||
set(key, value) {
|
||||
const realKey = `${key}`;
|
||||
this.config[realKey] = value;
|
||||
|
||||
return await this.save();
|
||||
return this.save();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConfigManager;
|
||||
export default ConfigManager;
|
||||
|
|
|
@ -1,30 +1,32 @@
|
|||
/**
|
||||
* The core / global reference object
|
||||
*
|
||||
* Version: v2.0.0
|
||||
* Developer: Marzavec ( https://github.com/marzavec )
|
||||
* License: WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const {
|
||||
import { join } from 'path';
|
||||
import {
|
||||
CommandManager,
|
||||
ConfigManager,
|
||||
ImportsManager,
|
||||
MainServer,
|
||||
StatsManager
|
||||
} = require('./');
|
||||
StatsManager,
|
||||
} from '.';
|
||||
|
||||
/**
|
||||
* The core app builds all required classes and maintains a central
|
||||
* reference point across the app
|
||||
* @property {ConfigManager} configManager - Provides loading and saving of the server config
|
||||
* @property {Object} config - The current json config object
|
||||
* @property {ImportsManager} dynamicImports - Dynamic require interface allowing hot reloading
|
||||
* @property {CommandManager} commands - Manages and executes command modules
|
||||
* @property {StatsManager} stats - Stores and adjusts arbritary stat data
|
||||
* @property {MainServer} server - Main websocket server reference
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @version v2.0.0
|
||||
* @license WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*/
|
||||
class CoreApp {
|
||||
/**
|
||||
* Create the main core instance.
|
||||
*/
|
||||
constructor () {
|
||||
|
||||
}
|
||||
|
||||
async init () {
|
||||
* Load config then initialize children
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
async init() {
|
||||
await this.buildConfigManager();
|
||||
|
||||
this.buildImportManager();
|
||||
|
@ -33,8 +35,14 @@ class CoreApp {
|
|||
this.buildMainServer();
|
||||
}
|
||||
|
||||
async buildConfigManager () {
|
||||
this.configManager = new ConfigManager(path.join(__dirname, '../..'));
|
||||
/**
|
||||
* Creates a new instance of the ConfigManager, loads and checks
|
||||
* the server config
|
||||
* @private
|
||||
* @return {void}
|
||||
*/
|
||||
async buildConfigManager() {
|
||||
this.configManager = new ConfigManager(join(__dirname, '../..'));
|
||||
this.config = await this.configManager.load();
|
||||
|
||||
if (this.config === false) {
|
||||
|
@ -43,23 +51,43 @@ class CoreApp {
|
|||
}
|
||||
}
|
||||
|
||||
buildImportManager () {
|
||||
this.dynamicImports = new ImportsManager(path.join(__dirname, '../..'));
|
||||
/**
|
||||
* Creates a new instance of the ImportsManager
|
||||
* @private
|
||||
* @return {void}
|
||||
*/
|
||||
buildImportManager() {
|
||||
this.dynamicImports = new ImportsManager(join(__dirname, '../..'));
|
||||
}
|
||||
|
||||
buildCommandsManager () {
|
||||
/**
|
||||
* Creates a new instance of the CommandManager and loads the command modules
|
||||
* @private
|
||||
* @return {void}
|
||||
*/
|
||||
buildCommandsManager() {
|
||||
this.commands = new CommandManager(this);
|
||||
this.commands.loadCommands();
|
||||
}
|
||||
|
||||
buildStatsManager () {
|
||||
/**
|
||||
* Creates a new instance of the StatsManager and sets the server start time
|
||||
* @private
|
||||
* @return {void}
|
||||
*/
|
||||
buildStatsManager() {
|
||||
this.stats = new StatsManager(this);
|
||||
this.stats.set('start-time', process.hrtime());
|
||||
}
|
||||
|
||||
buildMainServer () {
|
||||
/**
|
||||
* Creates a new instance of the MainServer
|
||||
* @private
|
||||
* @return {void}
|
||||
*/
|
||||
buildMainServer() {
|
||||
this.server = new MainServer(this);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CoreApp;
|
||||
export { CoreApp };
|
||||
|
|
|
@ -1,69 +1,77 @@
|
|||
import {
|
||||
resolve,
|
||||
basename as _basename,
|
||||
relative,
|
||||
} from 'path';
|
||||
import RecursiveRead from 'readdir-recursive';
|
||||
|
||||
/**
|
||||
* Import managment base, used to load commands/protocol and configuration objects
|
||||
*
|
||||
* Version: v2.0.0
|
||||
* Developer: Marzavec ( https://github.com/marzavec )
|
||||
* License: WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*
|
||||
* @property {String} base - Base path that all imports are required in from
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @version v2.0.0
|
||||
* @license WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*/
|
||||
|
||||
const read = require('readdir-recursive');
|
||||
const path = require('path');
|
||||
|
||||
class ImportsManager {
|
||||
/**
|
||||
* Create a `ImportsManager` instance for (re)loading classes and config
|
||||
*
|
||||
* Create an `ImportsManager` instance for (re)loading classes and config
|
||||
* @param {String} basePath executing directory name; default __dirname
|
||||
*/
|
||||
constructor (basePath) {
|
||||
constructor(basePath) {
|
||||
/**
|
||||
* Stored reference to the base directory path
|
||||
* @type {String}
|
||||
*/
|
||||
this.basePath = basePath;
|
||||
|
||||
/**
|
||||
* Data holder for imported modules
|
||||
* @type {Object}
|
||||
*/
|
||||
this.imports = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull base path that all imports are required in from
|
||||
*
|
||||
* @public
|
||||
* @type {String} readonly
|
||||
*/
|
||||
get base () {
|
||||
get base() {
|
||||
return this.basePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather all js files from target directory, then verify and load
|
||||
*
|
||||
* @param {String} dirName The name of the dir to load, relative to the basePath.
|
||||
*
|
||||
* @param {String} dirName The name of the dir to load, relative to the basePath
|
||||
* @private
|
||||
* @return {String} Load errors or empty if none
|
||||
*/
|
||||
loadDir (dirName) {
|
||||
const dir = path.resolve(this.basePath, dirName);
|
||||
loadDir(dirName) {
|
||||
const dir = resolve(this.basePath, dirName);
|
||||
|
||||
let errorText = '';
|
||||
try {
|
||||
read.fileSync(dir).forEach(file => {
|
||||
const basename = path.basename(file);
|
||||
RecursiveRead.fileSync(dir).forEach((file) => {
|
||||
const basename = _basename(file);
|
||||
if (basename.startsWith('_') || !basename.endsWith('.js')) return;
|
||||
|
||||
let imported;
|
||||
try {
|
||||
imported = require(file);
|
||||
|
||||
if (!this.imports[dirName]) {
|
||||
this.imports[dirName] = {};
|
||||
}
|
||||
|
||||
this.imports[dirName][file] = imported;
|
||||
} catch (e) {
|
||||
let err = `Unable to load modules from ${dirName} (${path.relative(dir, file)})\n${e}`;
|
||||
const err = `Unable to load modules from ${dirName} (${relative(dir, file)})\n${e}`;
|
||||
errorText += err;
|
||||
console.error(err);
|
||||
return errorText;
|
||||
}
|
||||
|
||||
if (!this.imports[dirName]) {
|
||||
this.imports[dirName] = {};
|
||||
}
|
||||
|
||||
this.imports[dirName][file] = imported;
|
||||
});
|
||||
} catch (e) {
|
||||
let err = `Unable to load modules from ${dirName}\n${e}`;
|
||||
const err = `Unable to load modules from ${dirName}\n${e}`;
|
||||
errorText += err;
|
||||
console.error(err);
|
||||
return errorText;
|
||||
|
@ -75,15 +83,13 @@ class ImportsManager {
|
|||
/**
|
||||
* Unlink references to each loaded module, pray to google that gc knows it's job,
|
||||
* then reinitialize this class to start the reload
|
||||
*
|
||||
* @param {Array} dirName The name of the dir to load, relative to the _base path.
|
||||
*
|
||||
* @public
|
||||
* @return {String} Load errors or empty if none
|
||||
*/
|
||||
reloadDirCache () {
|
||||
reloadDirCache() {
|
||||
let errorText = '';
|
||||
|
||||
Object.keys(this.imports).forEach(dir => {
|
||||
Object.keys(this.imports).forEach((dir) => {
|
||||
Object.keys(this.imports[dir]).forEach((mod) => {
|
||||
delete require.cache[require.resolve(mod)];
|
||||
});
|
||||
|
@ -97,20 +103,19 @@ class ImportsManager {
|
|||
/**
|
||||
* Pull reference to imported modules that were imported from dirName, or
|
||||
* load required directory if not found
|
||||
*
|
||||
* @param {String} dirName The name of the dir to load, relative to the _base path.
|
||||
*
|
||||
* @public
|
||||
* @return {Object} Object containing command module paths and structs
|
||||
*/
|
||||
getImport (dirName) {
|
||||
let imported = this.imports[dirName];
|
||||
getImport(dirName) {
|
||||
const imported = this.imports[dirName];
|
||||
|
||||
if (!imported) {
|
||||
this.loadDir(dirName);
|
||||
}
|
||||
|
||||
return Object.assign({}, this.imports[dirName]);
|
||||
return { ...this.imports[dirName] };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ImportsManager;
|
||||
export default ImportsManager;
|
||||
|
|
|
@ -1,58 +1,96 @@
|
|||
import {
|
||||
Server as WsServer,
|
||||
OPEN as SocketReady,
|
||||
} from 'ws';
|
||||
import { createHash } from 'crypto';
|
||||
import RateLimiter from './RateLimiter';
|
||||
|
||||
import { ServerConst } from '../utility/Constants';
|
||||
|
||||
/**
|
||||
* Main websocket server handling communications and connection events
|
||||
*
|
||||
* Version: v2.0.0
|
||||
* Developer: Marzavec ( https://github.com/marzavec )
|
||||
* License: WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*
|
||||
* @property {RateLimiter} police - Main rate limit handler
|
||||
* @property {String} cmdKey - Internal use command key
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @version v2.0.0
|
||||
* @license WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*/
|
||||
|
||||
const WsServer = require('ws').Server;
|
||||
const SocketReady = require('ws').OPEN;
|
||||
const Crypto = require('crypto');
|
||||
const RateLimiter = require('./RateLimiter');
|
||||
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 {
|
||||
/**
|
||||
* Create a HackChat server instance.
|
||||
*
|
||||
* @param {Object} core Reference to the global core object
|
||||
*/
|
||||
constructor (core) {
|
||||
* Create a HackChat server instance
|
||||
* @param {CoreApp} core Reference to the global core object
|
||||
*/
|
||||
constructor(core) {
|
||||
super({ port: core.config.websocketPort });
|
||||
|
||||
/**
|
||||
* Stored reference to the core
|
||||
* @type {CoreApp}
|
||||
*/
|
||||
this.core = core;
|
||||
|
||||
/**
|
||||
* Command key used to verify internal commands
|
||||
* @type {String}
|
||||
*/
|
||||
this.internalCmdKey = [...Array(Math.floor(Math.random() * 128) + 128)].map(() => (~~(Math.random() * 36)).toString(36)).join('');
|
||||
|
||||
/**
|
||||
* Salt used to hash a clients ip
|
||||
* @type {String}
|
||||
*/
|
||||
this.ipSalt = [...Array(Math.floor(Math.random() * 128) + 128)].map(() => (~~(Math.random() * 36)).toString(36)).join('');
|
||||
|
||||
/**
|
||||
* Data store for command hooks
|
||||
* @type {Object}
|
||||
*/
|
||||
this.hooks = {};
|
||||
|
||||
/**
|
||||
* Main rate limit tracker
|
||||
* @type {RateLimiter}
|
||||
*/
|
||||
this.police = new RateLimiter();
|
||||
|
||||
/**
|
||||
* Black listed command names
|
||||
* @type {Object}
|
||||
*/
|
||||
this.cmdBlacklist = {};
|
||||
|
||||
/**
|
||||
* Stored info about the last server error
|
||||
* @type {ErrorEvent}
|
||||
*/
|
||||
this.lastErr = null;
|
||||
|
||||
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
|
||||
* originate internally and not from a connected client
|
||||
* @todo Update to a structure that cannot be passed through json
|
||||
* @type {String}
|
||||
* @public
|
||||
* @readonly
|
||||
*/
|
||||
get cmdKey () {
|
||||
return InternalCmdKey;
|
||||
get cmdKey() {
|
||||
return this.internalCmdKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create ping interval and setup server event listeners
|
||||
*
|
||||
* @private
|
||||
* @return {void}
|
||||
*/
|
||||
setupServer () {
|
||||
this.heartBeat = setInterval(() => this.beatHeart(), PulseSpeed);
|
||||
setupServer() {
|
||||
this.heartBeat = setInterval(() => this.beatHeart(), ServerConst.PulseSpeed);
|
||||
|
||||
this.on('error', (err) => {
|
||||
this.handleError('server', err);
|
||||
this.handleError(err);
|
||||
});
|
||||
|
||||
this.on('connection', (socket, request) => {
|
||||
|
@ -62,66 +100,71 @@ class MainServer extends WsServer {
|
|||
|
||||
/**
|
||||
* Send empty `ping` frame to each client
|
||||
*
|
||||
* @private
|
||||
* @return {void}
|
||||
*/
|
||||
beatHeart () {
|
||||
let targetSockets = this.findSockets({});
|
||||
beatHeart() {
|
||||
const targetSockets = this.findSockets({});
|
||||
|
||||
if (targetSockets.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0, l = targetSockets.length; i < l; i++) {
|
||||
for (let i = 0, l = targetSockets.length; i < l; i += 1) {
|
||||
try {
|
||||
if (targetSockets[i].readyState === SocketReady) {
|
||||
targetSockets[i].ping();
|
||||
}
|
||||
} catch (e) { }
|
||||
} catch (e) { /* yolo */ }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind listeners for the new socket created on connection to this class
|
||||
*
|
||||
* @param {Object} socket New socket object
|
||||
* @param {ws#WebSocket} socket New socket object
|
||||
* @param {Object} request Initial headers of the new connection
|
||||
* @private
|
||||
* @return {void}
|
||||
*/
|
||||
newConnection (socket, request) {
|
||||
socket.remoteAddress = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
|
||||
newConnection(socket, request) {
|
||||
const newSocket = socket;
|
||||
|
||||
socket.on('message', (data) => {
|
||||
newSocket.address = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
|
||||
|
||||
newSocket.on('message', (data) => {
|
||||
this.handleData(socket, data);
|
||||
});
|
||||
|
||||
socket.on('close', () => {
|
||||
newSocket.on('close', () => {
|
||||
this.handleClose(socket);
|
||||
});
|
||||
|
||||
socket.on('error', (err) => {
|
||||
this.handleError(socket, err);
|
||||
newSocket.on('error', (err) => {
|
||||
this.handleError(err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming messages from clients, parse and check command, then hand-off
|
||||
*
|
||||
* @param {Object} socket Calling socket object
|
||||
* @param {ws#WebSocket} socket Calling socket object
|
||||
* @param {String} data Message sent from client
|
||||
* @private
|
||||
* @return {void}
|
||||
*/
|
||||
handleData (socket, data) {
|
||||
handleData(socket, data) {
|
||||
// Don't penalize yet, but check whether IP is rate-limited
|
||||
if (this.police.frisk(socket.remoteAddress, 0)) {
|
||||
if (this.police.frisk(socket.address, 0)) {
|
||||
this.core.commands.handleCommand(this, socket, {
|
||||
cmd: 'socketreply',
|
||||
cmdKey: this.cmdKey,
|
||||
text: 'You are being rate-limited or blocked.'
|
||||
text: 'You are being rate-limited or blocked.',
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Penalize here, but don't do anything about it
|
||||
this.police.frisk(socket.remoteAddress, 1);
|
||||
this.police.frisk(socket.address, 1);
|
||||
|
||||
// Ignore ridiculously large packets
|
||||
if (data.length > 65536) {
|
||||
|
@ -141,11 +184,11 @@ class MainServer extends WsServer {
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO: make this more flexible
|
||||
/*
|
||||
* Issue #1: hard coded `cmd` check
|
||||
* Issue #2: hard coded `cmd` value checks
|
||||
*/
|
||||
/**
|
||||
* @todo make the following more flexible
|
||||
* Issue #1: hard coded `cmd` check
|
||||
* Issue #2: hard coded `cmd` value checks
|
||||
*/
|
||||
if (typeof payload.cmd === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
@ -161,7 +204,7 @@ class MainServer extends WsServer {
|
|||
if (typeof this.cmdBlacklist[payload.cmd] === 'function') {
|
||||
return;
|
||||
}
|
||||
// End TODO //
|
||||
// End @todo //
|
||||
|
||||
// Execute `in` (incoming data) hooks and process results
|
||||
payload = this.executeHooks('in', socket, payload);
|
||||
|
@ -171,11 +214,11 @@ class MainServer extends WsServer {
|
|||
this.core.commands.handleCommand(this, socket, {
|
||||
cmd: 'socketreply',
|
||||
cmdKey: this.cmdKey,
|
||||
text: payload
|
||||
text: payload,
|
||||
});
|
||||
|
||||
return;
|
||||
} else if (payload === false) {
|
||||
} if (payload === false) {
|
||||
// A hook requested this data be dropped
|
||||
return;
|
||||
}
|
||||
|
@ -185,87 +228,107 @@ class MainServer extends WsServer {
|
|||
}
|
||||
|
||||
/**
|
||||
* Handle socket close from clients
|
||||
*
|
||||
* @param {Object} socket Closing socket object
|
||||
* Pass socket close event to disconnection command module
|
||||
* @param {ws#WebSocket} socket Closing socket object
|
||||
* @private
|
||||
* @return {void}
|
||||
*/
|
||||
handleClose (socket) {
|
||||
handleClose(socket) {
|
||||
this.core.commands.handleCommand(this, socket, {
|
||||
cmd: 'disconnect',
|
||||
cmdKey: this.cmdKey
|
||||
cmdKey: this.cmdKey,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* "Handle" server or socket errors
|
||||
*
|
||||
* @param {Object||String} socket Calling socket object, or 'server'
|
||||
* @param {String} err The sad stuff
|
||||
* @param {ErrorEvent} err The sad stuff
|
||||
* @private
|
||||
* @return {void}
|
||||
*/
|
||||
handleError (socket, err) {
|
||||
handleError(err) {
|
||||
this.lastErr = err;
|
||||
console.log(`Server error: ${err}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send data payload to specific socket/client
|
||||
*
|
||||
* @param {Object} payload Object to convert to json for transmission
|
||||
* @param {Object} socket The target client
|
||||
* @param {ws#WebSocket} socket The target client
|
||||
* @example
|
||||
* server.send({
|
||||
* cmd: 'info',
|
||||
* text: 'Only targetSocket will see this'
|
||||
* }, targetSocket);
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
send (payload, socket) {
|
||||
send(payload, socket) {
|
||||
let outgoingPayload = payload;
|
||||
|
||||
// Add timestamp to command
|
||||
payload.time = Date.now();
|
||||
outgoingPayload.time = Date.now();
|
||||
|
||||
// Execute `in` (incoming data) hooks and process results
|
||||
payload = this.executeHooks('out', socket, payload);
|
||||
outgoingPayload = this.executeHooks('out', socket, outgoingPayload);
|
||||
|
||||
if (typeof payload === 'string') {
|
||||
if (typeof outgoingPayload === 'string') {
|
||||
// A hook malfunctioned, reply with error
|
||||
this.core.commands.handleCommand(this, socket, {
|
||||
cmd: 'socketreply',
|
||||
cmdKey: this.cmdKey,
|
||||
text: payload
|
||||
text: outgoingPayload,
|
||||
});
|
||||
|
||||
return;
|
||||
} else if (payload === false) {
|
||||
} if (outgoingPayload === false) {
|
||||
// A hook requested this data be dropped
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (socket.readyState === SocketReady) {
|
||||
socket.send(JSON.stringify(payload));
|
||||
socket.send(JSON.stringify(outgoingPayload));
|
||||
}
|
||||
} catch (e) { }
|
||||
} catch (e) { /* yolo */ }
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload function for `this.send()`
|
||||
*
|
||||
* @param {Object} payload Object to convert to json for transmission
|
||||
* @param {Object} socket The target client
|
||||
* @param {ws#WebSocket} socket The target client
|
||||
* @example
|
||||
* server.reply({
|
||||
* cmd: 'info',
|
||||
* text: 'Only targetSocket will see this'
|
||||
* }, targetSocket);
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
reply (payload, socket) {
|
||||
reply(payload, socket) {
|
||||
this.send(payload, socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds sockets/clients that meet the filter requirements, then passes the data to them
|
||||
*
|
||||
* @param {Object} payload Object to convert to json for transmission
|
||||
* @param {Object} filter see `this.findSockets()`
|
||||
*
|
||||
* @example
|
||||
* server.broadcast({
|
||||
* cmd: 'info',
|
||||
* text: 'Everyone in "programming" will see this'
|
||||
* }, { channel: 'programming' });
|
||||
* @public
|
||||
* @return {Boolean} False if no clients matched the filter, true if data sent
|
||||
*/
|
||||
broadcast (payload, filter) {
|
||||
let targetSockets = this.findSockets(filter);
|
||||
broadcast(payload, filter) {
|
||||
const targetSockets = this.findSockets(filter);
|
||||
|
||||
if (targetSockets.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0, l = targetSockets.length; i < l; i++) {
|
||||
for (let i = 0, l = targetSockets.length; i < l; i += 1) {
|
||||
this.send(payload, targetSockets[i]);
|
||||
}
|
||||
|
||||
|
@ -274,51 +337,54 @@ class MainServer extends WsServer {
|
|||
|
||||
/**
|
||||
* Finds sockets/clients that meet the filter requirements, returns result as array
|
||||
*
|
||||
* @param {Object} data Object to convert to json for transmission
|
||||
* @param {Object} filter The socket must of equal or greater attribs matching `filter`
|
||||
* = {} // matches all
|
||||
* = { channel: 'programming' } // matches any socket where (`socket.channel` === 'programming')
|
||||
* = { channel: 'programming', nick: 'Marzavec' } // matches any socket where (`socket.channel` === 'programming' && `socket.nick` === 'Marzavec')
|
||||
*
|
||||
* @example
|
||||
* // match all sockets:
|
||||
* `filter` = {}
|
||||
* // match any socket where socket.channel === 'programming'
|
||||
* `filter` = { channel: 'programming' }
|
||||
* // match any socket where
|
||||
* // socket.channel === 'programming' && socket.nick === 'Marzavec'
|
||||
* `filter` = { channel: 'programming', nick: 'Marzavec' }
|
||||
* @public
|
||||
* @return {Array} Clients who matched the filter requirements
|
||||
*/
|
||||
findSockets (filter) {
|
||||
let filterAttribs = Object.keys(filter);
|
||||
let reqCount = filterAttribs.length;
|
||||
findSockets(filter) {
|
||||
const filterAttribs = Object.keys(filter);
|
||||
const reqCount = filterAttribs.length;
|
||||
let curMatch;
|
||||
let matches = [];
|
||||
for ( let socket of this.clients ) {
|
||||
const matches = [];
|
||||
this.clients.forEach((socket) => {
|
||||
// for (const socket of this.clients) {
|
||||
curMatch = 0;
|
||||
|
||||
for (let i = 0; i < reqCount; i++) {
|
||||
for (let i = 0; i < reqCount; i += 1) {
|
||||
if (typeof socket[filterAttribs[i]] !== 'undefined') {
|
||||
switch(typeof filter[filterAttribs[i]]) {
|
||||
switch (typeof filter[filterAttribs[i]]) {
|
||||
case 'object': {
|
||||
if (Array.isArray(filter[filterAttribs[i]])) {
|
||||
if (filter[filterAttribs[i]].indexOf(socket[filterAttribs[i]]) !== -1) {
|
||||
curMatch++;
|
||||
}
|
||||
} else {
|
||||
if (socket[filterAttribs[i]] === filter[filterAttribs[i]]) {
|
||||
curMatch++;
|
||||
curMatch += 1;
|
||||
}
|
||||
} else if (socket[filterAttribs[i]] === filter[filterAttribs[i]]) {
|
||||
curMatch += 1;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'function': {
|
||||
if (filter[filterAttribs[i]](socket[filterAttribs[i]])) {
|
||||
curMatch++;
|
||||
curMatch += 1;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
if (socket[filterAttribs[i]] === filter[filterAttribs[i]]) {
|
||||
curMatch++;
|
||||
curMatch += 1;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -327,7 +393,7 @@ class MainServer extends WsServer {
|
|||
if (curMatch === reqCount) {
|
||||
matches.push(socket);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
@ -335,18 +401,20 @@ class MainServer extends WsServer {
|
|||
/**
|
||||
* Hashes target socket's remote address using non-static variable length salt
|
||||
* encodes and shortens the output, returns that value
|
||||
*
|
||||
* @param {Object||String} target Either the target socket or ip as string
|
||||
*
|
||||
* @param {(ws#WebSocket|String)} target Either the target socket or ip as string
|
||||
* @example
|
||||
* let userHash = server.getSocketHash('1.2.3.4');
|
||||
* let userHash = server.getSocketHash(client);
|
||||
* @public
|
||||
* @return {String} Hashed client connection string
|
||||
*/
|
||||
getSocketHash (target) {
|
||||
let sha = Crypto.createHash('sha256');
|
||||
getSocketHash(target) {
|
||||
const sha = createHash('sha256');
|
||||
|
||||
if (typeof target === 'string') {
|
||||
sha.update(target + IpSalt);
|
||||
sha.update(target + this.ipSalt);
|
||||
} else {
|
||||
sha.update(target.remoteAddress + IpSalt);
|
||||
sha.update(target.address + this.ipSalt);
|
||||
}
|
||||
|
||||
return sha.digest('base64').substr(0, 15);
|
||||
|
@ -355,9 +423,10 @@ class MainServer extends WsServer {
|
|||
/**
|
||||
* (Re)loads all command module hooks, then sorts their order of operation by
|
||||
* priority, ascending (0 being highest priority)
|
||||
*
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
loadHooks () {
|
||||
loadHooks() {
|
||||
// clear current hooks (if any)
|
||||
this.clearHooks();
|
||||
// notify each module to register their hooks (if any)
|
||||
|
@ -366,23 +435,23 @@ class MainServer extends WsServer {
|
|||
let curHooks = [];
|
||||
let hookObj = [];
|
||||
|
||||
if (typeof this.hooks['in'] !== 'undefined') {
|
||||
if (typeof this.hooks.in !== 'undefined') {
|
||||
// start sorting, with incoming first
|
||||
curHooks = [ ...this.hooks['in'].keys() ];
|
||||
for (let i = 0, j = curHooks.length; i < j; i++) {
|
||||
hookObj = this.hooks['in'].get(curHooks[i]);
|
||||
hookObj.sort( (h1, h2) => h1.priority - h2.priority );
|
||||
this.hooks['in'].set(hookObj);
|
||||
curHooks = [...this.hooks.in.keys()];
|
||||
for (let i = 0, j = curHooks.length; i < j; i += 1) {
|
||||
hookObj = this.hooks.in.get(curHooks[i]);
|
||||
hookObj.sort((h1, h2) => h1.priority - h2.priority);
|
||||
this.hooks.in.set(hookObj);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof this.hooks['out'] !== 'undefined') {
|
||||
if (typeof this.hooks.out !== 'undefined') {
|
||||
// then outgoing
|
||||
curHooks = [ ...this.hooks['out'].keys() ];
|
||||
for (let i = 0, j = curHooks.length; i < j; i++) {
|
||||
hookObj = this.hooks['out'].get(curHooks[i]);
|
||||
hookObj.sort( (h1, h2) => h1.priority - h2.priority );
|
||||
this.hooks['out'].set(hookObj);
|
||||
curHooks = [...this.hooks.out.keys()];
|
||||
for (let i = 0, j = curHooks.length; i < j; i += 1) {
|
||||
hookObj = this.hooks.out.get(curHooks[i]);
|
||||
hookObj.sort((h1, h2) => h1.priority - h2.priority);
|
||||
this.hooks.out.set(hookObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -391,17 +460,19 @@ class MainServer extends WsServer {
|
|||
* Adds a target function to an array of hooks. Hooks are executed either before
|
||||
* processing user input (`in`) or before sending data back to the client (`out`)
|
||||
* and allows a module to modify each payload before moving forward
|
||||
*
|
||||
* @param {String} type The type of event, typically `in` (incoming) or `out` (outgoing)
|
||||
* @param {String} command Should match the desired `cmd` attrib of the payload
|
||||
* @param {Function} hookFunction Target function to execute, should accept `server`, `socket` and `payload` as parameters
|
||||
* @param {Number} priority Execution priority, hooks with priority 1 will be executed before hooks with priority 200 for example
|
||||
* @param {Function} hookFunction Target function to execute, should accept
|
||||
* `server`, `socket` and `payload` as parameters
|
||||
* @param {Number} priority Execution priority, hooks with priority 1 will be executed before
|
||||
* hooks with priority 200 for example
|
||||
* @example
|
||||
* // Create hook to add "and stuff" to every chat line
|
||||
* server.registerHook('in', 'chat', (server, socket, payload) => payload.text += ' and stuff');
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
registerHook (type, command, hookFunction, priority) {
|
||||
if (typeof priority === 'undefined') {
|
||||
priority = 25;
|
||||
}
|
||||
|
||||
registerHook(type, command, hookFunction, priority = 25) {
|
||||
if (typeof this.hooks[type] === 'undefined') {
|
||||
this.hooks[type] = new Map();
|
||||
}
|
||||
|
@ -412,7 +483,7 @@ class MainServer extends WsServer {
|
|||
|
||||
this.hooks[type].get(command).push({
|
||||
run: hookFunction,
|
||||
priority: priority
|
||||
priority,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -422,25 +493,25 @@ class MainServer extends WsServer {
|
|||
* A payload (modified or not) that will continue through the data flow
|
||||
* A boolean false to indicate halting the data through flow
|
||||
* A string which indicates an error occured in executing the hook
|
||||
*
|
||||
* @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} payload Either incoming data from client or outgoing data (depending on `type`)
|
||||
*
|
||||
* @return {Object || Boolean}
|
||||
* @param {ws#WebSocket} socket Either target client or client (depends on `type`)
|
||||
* @param {Object} payload Either incoming data from client or outgoing data (depends on `type`)
|
||||
* @private
|
||||
* @return {Object|Boolean}
|
||||
*/
|
||||
executeHooks (type, socket, payload) {
|
||||
let command = payload.cmd;
|
||||
executeHooks(type, socket, payload) {
|
||||
const command = payload.cmd;
|
||||
let newPayload = payload;
|
||||
|
||||
if (typeof this.hooks[type] !== 'undefined') {
|
||||
if (this.hooks[type].has(command)) {
|
||||
let hooks = this.hooks[type].get(command);
|
||||
const 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 += 1) {
|
||||
try {
|
||||
payload = hooks[i].run(this.core, this, socket, payload);
|
||||
newPayload = hooks[i].run(this.core, this, socket, newPayload);
|
||||
} catch (err) {
|
||||
let errText = `Hook failure, '${type}', '${command}': `;
|
||||
const errText = `Hook failure, '${type}', '${command}': `;
|
||||
if (this.core.config.logErrDetailed === true) {
|
||||
console.log(errText + err.stack);
|
||||
} else {
|
||||
|
@ -450,23 +521,24 @@ class MainServer extends WsServer {
|
|||
}
|
||||
|
||||
// A hook function may choose to return false to prevent all further processing
|
||||
if (payload === false) {
|
||||
if (newPayload === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return payload;
|
||||
return newPayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipe server hooks to make ready for module reload calls
|
||||
*
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
clearHooks () {
|
||||
clearHooks() {
|
||||
this.hooks = {};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MainServer;
|
||||
export default MainServer;
|
||||
|
|
|
@ -1,39 +1,60 @@
|
|||
import { RateLimits } from '../utility/Constants';
|
||||
|
||||
/**
|
||||
* Tracks frequency of occurances based on `id` (remote address), then allows or
|
||||
* denies command execution based on comparison with `threshold`
|
||||
*
|
||||
* Version: v2.0.0
|
||||
* Developer: Marzavec ( https://github.com/marzavec )
|
||||
* License: WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*
|
||||
* @property {Object} data - The current stats data
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @author Andrew Belt ( https://github.com/AndrewBelt )
|
||||
* @version v2.0.0
|
||||
* @license WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*/
|
||||
|
||||
class RateLimiter {
|
||||
/**
|
||||
* Create a ratelimiter instance.
|
||||
*/
|
||||
constructor () {
|
||||
* Create a ratelimiter instance
|
||||
*/
|
||||
constructor() {
|
||||
/**
|
||||
* Data holder rate limit records
|
||||
* @type {Object}
|
||||
*/
|
||||
this.records = {};
|
||||
this.halflife = 30 * 1000; // milliseconds
|
||||
this.threshold = 25;
|
||||
|
||||
/**
|
||||
* Time in milliseconds to decrement ratelimit weight
|
||||
* @type {Number}
|
||||
*/
|
||||
this.halflife = RateLimits.halflife;
|
||||
|
||||
/**
|
||||
* Weight until ratelimited
|
||||
* @type {Number}
|
||||
*/
|
||||
this.threshold = RateLimits.threshold;
|
||||
|
||||
/**
|
||||
* Stores the associated connection fingerprint with record id
|
||||
* @type {Array}
|
||||
*/
|
||||
this.hashes = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds current score by `id`
|
||||
*
|
||||
* @param {String} id target id / address
|
||||
*
|
||||
* @private
|
||||
* @return {Object} Object containing the record meta
|
||||
*/
|
||||
search (id) {
|
||||
search(id) {
|
||||
let record = this.records[id];
|
||||
|
||||
if (!record) {
|
||||
record = this.records[id] = {
|
||||
this.records[id] = {
|
||||
time: Date.now(),
|
||||
score: 0
|
||||
}
|
||||
score: 0,
|
||||
};
|
||||
|
||||
record = this.records[id];
|
||||
}
|
||||
|
||||
return record;
|
||||
|
@ -41,20 +62,22 @@ class RateLimiter {
|
|||
|
||||
/**
|
||||
* Adjusts the current ratelimit score by `deltaScore`
|
||||
*
|
||||
* @param {String} id target id / address
|
||||
* @param {Number} deltaScore amount to adjust current score by
|
||||
*
|
||||
* @example
|
||||
* // Penalize by 1 and store if connection is ratelimited or not
|
||||
* let isLimited = police.frisk(socket.address, 1);
|
||||
* @public
|
||||
* @return {Boolean} True if record threshold has been exceeded
|
||||
*/
|
||||
frisk (id, deltaScore) {
|
||||
let record = this.search(id);
|
||||
frisk(id, deltaScore) {
|
||||
const record = this.search(id);
|
||||
|
||||
if (record.arrested) {
|
||||
return true;
|
||||
}
|
||||
|
||||
record.score *= Math.pow(2, -(Date.now() - record.time ) / this.halflife);
|
||||
record.score *= 2 ** -(Date.now() - record.time) / this.halflife;
|
||||
record.score += deltaScore;
|
||||
record.time = Date.now();
|
||||
|
||||
|
@ -67,11 +90,16 @@ class RateLimiter {
|
|||
|
||||
/**
|
||||
* Statically set server to no longer accept traffic from `id`
|
||||
*
|
||||
* @param {String} id target id / address
|
||||
* @example
|
||||
* // Usage within a command module:
|
||||
* let badClient = server.findSockets({ channel: socket.channel, nick: targetNick });
|
||||
* server.police.arrest(badClient[0].address, badClient[0].hash);
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
arrest (id, hash) {
|
||||
let record = this.search(id);
|
||||
arrest(id, hash) {
|
||||
const record = this.search(id);
|
||||
|
||||
record.arrested = true;
|
||||
this.hashes[hash] = id;
|
||||
|
@ -79,17 +107,32 @@ class RateLimiter {
|
|||
|
||||
/**
|
||||
* Remove statically assigned limit from `id`
|
||||
*
|
||||
* @param {String} id target id / address
|
||||
* @example
|
||||
* // Usage within a command module:
|
||||
* server.police.pardon('targetHashOrIP');
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
pardon (id) {
|
||||
if (typeof this.hashes[id] !== 'undefined') {
|
||||
id = this.hashes[id];
|
||||
pardon(id) {
|
||||
let targetId = id;
|
||||
if (typeof this.hashes[targetId] !== 'undefined') {
|
||||
targetId = this.hashes[targetId];
|
||||
}
|
||||
|
||||
let record = this.search(id);
|
||||
const record = this.search(targetId);
|
||||
record.arrested = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all records
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
clear() {
|
||||
this.records = {};
|
||||
this.hashes = [];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RateLimiter;
|
||||
export default RateLimiter;
|
||||
|
|
|
@ -1,60 +1,79 @@
|
|||
/**
|
||||
* Simple generic stats collection script for events occurances (etc)
|
||||
*
|
||||
* Version: v2.0.0
|
||||
* Developer: Marzavec ( https://github.com/marzavec )
|
||||
* License: WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*
|
||||
* @property {Object} data - The current stats data
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @version v2.0.0
|
||||
* @license WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
*/
|
||||
|
||||
class StatsManager {
|
||||
/**
|
||||
* Create a stats instance.
|
||||
*
|
||||
* Create a stats instance
|
||||
*/
|
||||
constructor () {
|
||||
constructor() {
|
||||
/**
|
||||
* Data holder for the stats class
|
||||
* @type {Object}
|
||||
*/
|
||||
this.data = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve value of arbitrary `key` reference
|
||||
*
|
||||
* @param {String} key Reference to the arbitrary store name
|
||||
*
|
||||
* @example
|
||||
* // Find previously set `start-time`
|
||||
* stats.get('start-time');
|
||||
* @public
|
||||
* @return {*} Data referenced by `key`
|
||||
*/
|
||||
get (key) {
|
||||
get(key) {
|
||||
return this.data[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value of arbitrary `key` reference
|
||||
*
|
||||
* @param {String} key Reference to the arbitrary store name
|
||||
* @param {Number} value New value for `key`
|
||||
* @example
|
||||
* // Set `start-time`
|
||||
* stats.set('start-time', process.hrtime());
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
set (key, value) {
|
||||
set(key, value) {
|
||||
this.data[key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase value of arbitrary `key` reference, by 1 or `amount`
|
||||
*
|
||||
* @param {String} key Reference to the arbitrary store name
|
||||
* @param {Number} amount Value to increase `key` by, or 1 if omitted
|
||||
* @param {?Number} [amount=1] Value to increase `key` by, or 1 if omitted
|
||||
* @example
|
||||
* // Increment by `amount`
|
||||
* stats.increment('users', 6);
|
||||
* // Increment by 1
|
||||
* stats.increment('users');
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
increment (key, amount) {
|
||||
this.set(key, (this.get(key) || 0) + (amount || 1));
|
||||
increment(key, amount = 1) {
|
||||
this.set(key, (this.get(key) || 0) + amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce value of arbitrary `key` reference, by 1 or `amount`
|
||||
*
|
||||
* @param {String} key Reference to the arbitrary store name
|
||||
* @param {Number} amount Value to decrease `key` by, or 1 if omitted
|
||||
* @param {?Number} [amount=1] Value to decrease `key` by, or 1 if omitted
|
||||
* @example
|
||||
* // Decrement by `amount`
|
||||
* stats.decrement('users', 6);
|
||||
* // Decrement by 1
|
||||
* stats.decrement('users');
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
decrement (key, amount) {
|
||||
this.set(key, (this.get(key) || 0) - (amount || 1));
|
||||
decrement(key, amount = 1) {
|
||||
this.set(key, (this.get(key) || 0) - amount);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
module.exports = {
|
||||
CommandManager: require('./CommandManager'),
|
||||
ConfigManager: require('./ConfigManager'),
|
||||
ImportsManager: require('./ImportsManager'),
|
||||
MainServer: require('./MainServer'),
|
||||
RateLimiter: require('./RateLimiter'),
|
||||
StatsManager: require('./StatsManager')
|
||||
};
|
||||
export const CommandManager = require('./CommandManager').default;
|
||||
export const ConfigManager = require('./ConfigManager').default;
|
||||
export const ImportsManager = require('./ImportsManager').default;
|
||||
export const MainServer = require('./MainServer').default;
|
||||
export const RateLimiter = require('./RateLimiter').default;
|
||||
export const StatsManager = require('./StatsManager');
|
||||
|
|
19
server/src/utility/Constants.js
Normal file
19
server/src/utility/Constants.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Rate limit options
|
||||
* @typedef {Object} RateLimits
|
||||
* @property {number} halflife Time in milliseconds to decrement ratelimit weight
|
||||
* @property {number} threshold Weight until ratelimited
|
||||
*/
|
||||
exports.RateLimits = {
|
||||
halflife: 30 * 1000,
|
||||
threshold: 25,
|
||||
};
|
||||
|
||||
/**
|
||||
* Websocket server options
|
||||
* @typedef {Object} ServerConst
|
||||
* @property {number} PulseSpeed Time in milliseconds to ping each client
|
||||
*/
|
||||
exports.ServerConst = {
|
||||
PulseSpeed: 16 * 1000,
|
||||
};
|
Loading…
Reference in New Issue
Block a user