mirror of
https://github.com/hack-chat/main.git
synced 2024-03-22 13:20:33 +08:00
commit
a3bca6d000
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
3
.eslintignore
Normal file
3
.eslintignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
/test/*.js
|
||||
/client/*
|
||||
/documentation/*
|
33
.eslintrc.cjs
Normal file
33
.eslintrc.cjs
Normal file
|
@ -0,0 +1,33 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
'airbnb-base',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
"no-console": 0,
|
||||
"no-param-reassign": [
|
||||
"error",
|
||||
{
|
||||
"props": false,
|
||||
}
|
||||
],
|
||||
"import/extensions": [
|
||||
"error",
|
||||
"ignorePackages",
|
||||
{
|
||||
js: "always",
|
||||
jsx: "always",
|
||||
ts: "always",
|
||||
tsx: "always",
|
||||
mjs: "always",
|
||||
}
|
||||
]
|
||||
},
|
||||
};
|
107
.gitattributes
vendored
Normal file
107
.gitattributes
vendored
Normal file
|
@ -0,0 +1,107 @@
|
|||
# From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes
|
||||
|
||||
# Handle line endings automatically for files detected as text
|
||||
# and leave all files detected as binary untouched.
|
||||
* text=auto
|
||||
|
||||
#
|
||||
# The above will handle all files NOT found below
|
||||
#
|
||||
|
||||
#
|
||||
## These files are text and should be normalized (Convert crlf => lf)
|
||||
#
|
||||
|
||||
# source code
|
||||
*.php text
|
||||
*.css text
|
||||
*.sass text
|
||||
*.scss text
|
||||
*.less text
|
||||
*.styl text
|
||||
*.js text eol=lf
|
||||
*.coffee text
|
||||
*.json text
|
||||
*.htm text
|
||||
*.html text
|
||||
*.xml text
|
||||
*.svg text
|
||||
*.txt text
|
||||
*.ini text
|
||||
*.inc text
|
||||
*.pl text
|
||||
*.rb text
|
||||
*.py text
|
||||
*.scm text
|
||||
*.sql text
|
||||
*.sh text
|
||||
*.bat text
|
||||
|
||||
# templates
|
||||
*.ejs text
|
||||
*.hbt text
|
||||
*.jade text
|
||||
*.haml text
|
||||
*.hbs text
|
||||
*.dot text
|
||||
*.tmpl text
|
||||
*.phtml text
|
||||
|
||||
# server config
|
||||
.htaccess text
|
||||
.nginx.conf text
|
||||
|
||||
# git config
|
||||
.gitattributes text
|
||||
.gitignore text
|
||||
.gitconfig text
|
||||
|
||||
# code analysis config
|
||||
.jshintrc text
|
||||
.jscsrc text
|
||||
.jshintignore text
|
||||
.csslintrc text
|
||||
|
||||
# misc config
|
||||
*.yaml text
|
||||
*.yml text
|
||||
.editorconfig text
|
||||
|
||||
# build config
|
||||
*.npmignore text
|
||||
*.bowerrc text
|
||||
|
||||
# Heroku
|
||||
Procfile text
|
||||
.slugignore text
|
||||
|
||||
# Documentation
|
||||
*.md text
|
||||
LICENSE text
|
||||
AUTHORS text
|
||||
|
||||
|
||||
#
|
||||
## These files are binary and should be left untouched
|
||||
#
|
||||
|
||||
# (binary is a macro for -text -diff)
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.mov binary
|
||||
*.mp4 binary
|
||||
*.mp3 binary
|
||||
*.flv binary
|
||||
*.fla binary
|
||||
*.swf binary
|
||||
*.gz binary
|
||||
*.zip binary
|
||||
*.7z binary
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.woff binary
|
||||
*.pyc binary
|
||||
*.pdf binary
|
85
.gitignore
vendored
85
.gitignore
vendored
|
@ -4,6 +4,11 @@ logs
|
|||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
|
@ -15,12 +20,13 @@ pids
|
|||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
.coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
@ -36,8 +42,11 @@ build/Release
|
|||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
@ -45,6 +54,15 @@ typings/
|
|||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
|
@ -54,10 +72,63 @@ typings/
|
|||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# next.js build output
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
server/config/
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
session.key
|
||||
salt.key
|
||||
config.json
|
||||
|
|
1
.hcserver.json
Normal file
1
.hcserver.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"modulesPath":"./commands","websocketPort":"6060","rateLimit":{"halflife":"30000","threshold":"25"},"pulseSpeed":"16000"}
|
29
.nycrc
Normal file
29
.nycrc
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"check-coverage": true,
|
||||
"per-file": true,
|
||||
"lines": 80,
|
||||
"statements": 80,
|
||||
"functions": 80,
|
||||
"branches": 80,
|
||||
"include": [
|
||||
"commands/**/*.js"
|
||||
],
|
||||
"exclude": [
|
||||
"commands/**/*.spec.js"
|
||||
],
|
||||
"ignore-class-method": "methodToIgnore",
|
||||
"reporter": [
|
||||
"html",
|
||||
"lcov",
|
||||
"text",
|
||||
"text-summary"
|
||||
],
|
||||
"require": [
|
||||
"dotenv/config"
|
||||
],
|
||||
"extension": [],
|
||||
"cache": true,
|
||||
"all": true,
|
||||
"temp-dir": "",
|
||||
"report-dir": "./.coverage"
|
||||
}
|
147
CHANGELOG.md
147
CHANGELOG.md
|
@ -1,147 +0,0 @@
|
|||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.1.93 pre 2.2] - 2020-03-12
|
||||
### Added
|
||||
- (Source) `./pm2.config.js` PM2 ecosystem config handling both http-server and the websocket
|
||||
- (Source) NPM new commands:
|
||||
- "start": Starts or reloads the dev environment
|
||||
- "stop": Stops and clears the dev environment
|
||||
- "logs": Show / watch http & websocket logs for errors and events
|
||||
- "clear": Clear all logged data
|
||||
- "status": Show status of http and websocket
|
||||
- "refresh": Clears logged data and stops http and websocket
|
||||
- (Server) Numeric user levels / UAC, related to issue #86
|
||||
- (Server) `join` module password property, related to V2 protocol update
|
||||
- (Server) `users` array to `onlineSet` structure, related to V2 protocol update
|
||||
- (Server) `session` module, related to V2 protocol update
|
||||
- (Server) `/move` chat hook to `move` module
|
||||
|
||||
### Removed
|
||||
- (Source) `./clientSource/` directory
|
||||
|
||||
### Changed
|
||||
- (Server) Minor bug fixes
|
||||
- (Server) Increased module abstraction to remove duplicate code (thanks @MinusGix)
|
||||
|
||||
## [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 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
|
||||
- (Client) Fixed firefox drop down menu bug
|
||||
- (Client) Updated Katex lib
|
||||
|
||||
### Stretched
|
||||
- The term "minimal"
|
||||
|
||||
## [2.1.9 pre 2.2] - 2019-03-18
|
||||
### Changed
|
||||
- Configuration script setup, making it more portable/sane
|
||||
- Refactored naming scheme and entry point
|
||||
|
||||
### Removed
|
||||
- Configuration setup from `./serverLib/ConfigManager`
|
||||
- Unused feature allowing command modules to add to the configuration/setup process
|
||||
- `deasync` dependency
|
||||
|
||||
## [2.1.9] - 2019-02-21
|
||||
### Added
|
||||
- `./server/src/commands/core/emote.js` module to provide action text
|
||||
- `./server/src/core/server.js` priorities to command hooking
|
||||
- Priority levels to all command modules
|
||||
- `./server/src/commands/core/chat.js` Unknown '/' commands will now return a warning
|
||||
- `./server/src/commands/internal/legacylayer.js` to provide compatibility to legacy connections
|
||||
|
||||
### Changed
|
||||
- Updated all libraries to latest
|
||||
- `./server/src/core/server.js` Removed unneeded function bindings
|
||||
- `./server/src/core/server.js` Hook function layout
|
||||
- `./server/src/managers/config.js` Documentation wording
|
||||
|
||||
## [2.1.0] - 2018-09-29
|
||||
### Added
|
||||
- Module hook framework, isolating modules and making them truly drop-to-install
|
||||
- `./server/src/commands/core/whisper.js` module to send in-channel private messages, `/whisper` hook
|
||||
- `muzzle` and `mute` aliases to `./server/src/commands/mod/dumb.js`
|
||||
- `unmuzzle` and `unmute` aliases to `./server/src/commands/mod/speak.js`
|
||||
- `./server/src/commands/admin/removemod.js` module to remove mods
|
||||
- `./server/src/commands/mod/unbanall.js` module to clear all bans and ratelimiting
|
||||
|
||||
### Changed
|
||||
- Further code cleanup on all modules
|
||||
- Adjusted `ipSalt` entropy
|
||||
- `./server/src/commands/core/help.js` output is now helpful, added `/help` hook
|
||||
- `./server/src/commands/core/chat.js` added `/myhash` and `/me` hooks
|
||||
- `./server/src/commands/core/morestats.js` added `/stats` hook
|
||||
|
||||
## [2.0.3] - 2018-06-03
|
||||
### Added
|
||||
- `./server/src/commands/mod/dumb.js` module for server-wide shadow muting
|
||||
- `./server/src/commands/mod/speak.js` module unmuting
|
||||
- `./server/src/commands/internal/socketreply.js` module to route warning to clients
|
||||
- `./server/src/commands/core/ping.js` module to prevent `didYouMean` errors on legacy sources
|
||||
|
||||
### Changed
|
||||
- Moved `disconnect.js` into servers internal modules directory
|
||||
- Restructured `server.js` and `commands.js`, removing hardcoded protocol use
|
||||
|
||||
## [2.0.2] - 2018-05-19
|
||||
### Added
|
||||
- `./documentation/DOCUMENTATION.md` document which gives overview of the applications protocol
|
||||
- `./documentation/DEPLOY.md` document which gives overview of deploying the server live
|
||||
- `./LICENSE` License file
|
||||
- Code highlighting, triggered with #
|
||||
|
||||
### Changed
|
||||
- `README.md` wording and layout
|
||||
|
||||
### Removed
|
||||
- Unneeded `use strict`
|
||||
|
||||
## [2.0.1] - 2018-04-18
|
||||
### Added
|
||||
- `users-kicked` tracking to `morestats` command
|
||||
- Server-side ping interval
|
||||
- `move` command to change channels without reconnecting
|
||||
- `disconnect` command module to free core server from protocol dependency
|
||||
- `changenick` command to change client nick without reconnecting
|
||||
|
||||
### Changed
|
||||
- Filter object of the `findSockets` function now accepts more complex parameters, including functions and arrays
|
||||
- `kick` command now accepts an array as the `nick` argument allowing multiple simultaneous kicks
|
||||
- `join` command now takes advantage of the new filter object
|
||||
- Core server disconnect handler now calls the `disconnect` module instead of broadcasting hard coded `onlineRemove`
|
||||
|
||||
### Removed
|
||||
- Client-side ping interval
|
||||
|
||||
## [2.0.0] - 2018-04-12
|
||||
### Added
|
||||
- CHANGELOG.md
|
||||
- `index.html` files to `katex` directories
|
||||
|
||||
### Changed
|
||||
- Updated client html KaTeX libraries to v0.9.0
|
||||
|
||||
### Removed
|
||||
- Uneeded files under `katex` directories
|
13
LICENSE
13
LICENSE
|
@ -1,13 +0,0 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
19
README.md
19
README.md
|
@ -4,14 +4,14 @@
|
|||
|
||||
A list of software developed for the hack.chat framework can be found at the [3rd party software list](https://github.com/hack-chat/3rd-party-software-list) repository. This includes bots, clients, docker containers, etc.
|
||||
|
||||
This is a backwards compatible continuation of the [work by Andrew Belt](https://github.com/AndrewBelt/hack.chat). The server code has been updated to ES6 along with several new features including new commands and hot-reload of the commands/protocol. There is also [documentation](documentation/DOCUMENTATION.md) and a [changelog](CHANGELOG.md).
|
||||
This is a backwards compatible continuation of the [work by Andrew Belt](https://github.com/AndrewBelt/hack.chat). The server code has been updated to ES6 along with several new features including new commands and hot-reload of the commands/protocol. There is also [documentation](documentation/index.html).
|
||||
|
||||
# Installation
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [node.js 8.10.0](https://nodejs.org/en/download/package-manager/#windows) or higher
|
||||
- [npm 5.7.1](https://nodejs.org/en/download/package-manager/#windows) or higher
|
||||
- [node.js v16.14.0](https://nodejs.org/) or higher
|
||||
- [npm 8.5.4](https://nodejs.org/) or higher
|
||||
|
||||
## Developer Installation
|
||||
|
||||
|
@ -20,19 +20,14 @@ This is a backwards compatible continuation of the [work by Andrew Belt](https:/
|
|||
1. Install the dependencies: `npm install`
|
||||
1. Launch: `npm start`
|
||||
|
||||
If you change the `websocketPort` option during the config setup then these changes will need to be reflected on [line 60 of client.js](https://github.com/hack-chat/main/blob/master/client/client.js#L60).
|
||||
|
||||
## Live Deployment Installation
|
||||
|
||||
See [DEPLOY.md](documentation/DEPLOY.md)
|
||||
|
||||
# Contributing
|
||||
|
||||
- If you are modifying commands, make sure it is backwards compatible with the legacy client and you update the documentation accordingly.
|
||||
- Use [the template](documentation/templateCommand.js) to learn how to create new commands.
|
||||
- Use two space indents.
|
||||
- Name files in camelCase.
|
||||
- Scripts that do not default to strict mode (such as modules) must use the `'use strict'` directive.
|
||||
|
||||
# Credits
|
||||
|
||||
|
@ -41,10 +36,10 @@ See [DEPLOY.md](documentation/DEPLOY.md)
|
|||
* [**Neel Kamath**](https://github.com/neelkamath) - *Base Documentation*
|
||||
* [**Carlos Villavicencio**](https://github.com/po5i) - *Syntax Highlighting Integration*
|
||||
* [**OpSimple**](https://github.com/OpSimple) - *Modules Added: dumb.js & speak.js*
|
||||
* Andrew Belt, https://github.com/AndrewBelt, for original base work
|
||||
* [wwandrew](https://github.com/wwandrew), for finding server flaws (including attack vectors) and submitting ~~___incredibly detailed___~~ bug reports
|
||||
* [Everyone else](https://github.com/hack-chat/main/graphs/contributors) who participated in this project.
|
||||
* [**Andrew Belt**](https://github.com/AndrewBelt), for original base work
|
||||
* [**wwandrew**](https://github.com/wwandrew), for finding server flaws (including attack vectors) and submitting ~~___incredibly detailed___~~ bug reports
|
||||
* [**Everyone else**](https://github.com/hack-chat/main/graphs/contributors) who participated in this project.
|
||||
|
||||
# License
|
||||
|
||||
This project is licensed under the [WTFPL License](LICENSE).
|
||||
This project is licensed under the [MIT License](LICENSE).
|
||||
|
|
193
client/client.js
193
client/client.js
|
@ -23,7 +23,7 @@ var markdownOptions = {
|
|||
langPrefix: '',
|
||||
linkify: true,
|
||||
linkTarget: '_blank" rel="noreferrer',
|
||||
typographer: true,
|
||||
typographer: true,
|
||||
quotes: `""''`,
|
||||
|
||||
doHighlight: true,
|
||||
|
@ -33,12 +33,12 @@ var markdownOptions = {
|
|||
if (lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
return hljs.highlight(lang, str).value;
|
||||
} catch (__) {}
|
||||
} catch (__) { }
|
||||
}
|
||||
|
||||
try {
|
||||
return hljs.highlightAuto(str).value;
|
||||
} catch (__) {}
|
||||
} catch (__) { }
|
||||
|
||||
return '';
|
||||
}
|
||||
|
@ -75,20 +75,20 @@ md.renderer.rules.image = function (tokens, idx, options) {
|
|||
return '<a href="' + src + '" target="_blank" rel="noreferrer"><img' + scrollOnload + imgSrc + alt + title + suffix + '></a>';
|
||||
}
|
||||
|
||||
return '<a href="' + src + '" target="_blank" rel="noreferrer">' + Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(src)) + '</a>';
|
||||
return '<a href="' + src + '" target="_blank" rel="noreferrer">' + Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(src)) + '</a>';
|
||||
};
|
||||
|
||||
md.renderer.rules.link_open = function (tokens, idx, options) {
|
||||
var title = tokens[idx].title ? (' title="' + Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(tokens[idx].title)) + '"') : '';
|
||||
var target = options.linkTarget ? (' target="' + options.linkTarget + '"') : '';
|
||||
return '<a rel="noreferrer" onclick="return verifyLink(this)" href="' + Remarkable.utils.escapeHtml(tokens[idx].href) + '"' + title + target + '>';
|
||||
var target = options.linkTarget ? (' target="' + options.linkTarget + '"') : '';
|
||||
return '<a rel="noreferrer" onclick="return verifyLink(this)" href="' + Remarkable.utils.escapeHtml(tokens[idx].href) + '"' + title + target + '>';
|
||||
};
|
||||
|
||||
md.renderer.rules.text = function(tokens, idx) {
|
||||
md.renderer.rules.text = function (tokens, idx) {
|
||||
tokens[idx].content = Remarkable.utils.escapeHtml(tokens[idx].content);
|
||||
|
||||
if (tokens[idx].content.indexOf('?') !== -1) {
|
||||
tokens[idx].content = tokens[idx].content.replace(/(^|\s)(\?)\S+?(?=[,.!?:)]?\s|$)/gm, function(match) {
|
||||
tokens[idx].content = tokens[idx].content.replace(/(^|\s)(\?)\S+?(?=[,.!?:)]?\s|$)/gm, function (match) {
|
||||
var channelLink = Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(match.trim()));
|
||||
var whiteSpace = '';
|
||||
if (match[0] !== '?') {
|
||||
|
@ -98,7 +98,7 @@ md.renderer.rules.text = function(tokens, idx) {
|
|||
});
|
||||
}
|
||||
|
||||
return tokens[idx].content;
|
||||
return tokens[idx].content;
|
||||
};
|
||||
|
||||
md.use(remarkableKatex);
|
||||
|
@ -172,12 +172,40 @@ var myChannel = window.location.search.replace(/^\?/, '');
|
|||
var lastSent = [""];
|
||||
var lastSentPos = 0;
|
||||
|
||||
/**
|
||||
* Stores active messages
|
||||
* These are messages that can be edited.
|
||||
* @type {{ customId: string, userid: number, sent: number, text: string, elem: HTMLElement }[]}
|
||||
*/
|
||||
var activeMessages = [];
|
||||
|
||||
setInterval(function () {
|
||||
var editTimeout = 6 * 60 * 1000;
|
||||
var now = Date.now();
|
||||
for (var i = 0; i < activeMessages.length; i++) {
|
||||
if (now - activeMessages[i].sent > editTimeout) {
|
||||
activeMessages.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}, 30 * 1000);
|
||||
|
||||
function addActiveMessage(customId, userid, text, elem) {
|
||||
activeMessages.push({
|
||||
customId,
|
||||
userid,
|
||||
sent: Date.now(),
|
||||
text,
|
||||
elem,
|
||||
});
|
||||
}
|
||||
|
||||
/** Notification switch and local storage behavior **/
|
||||
var notifySwitch = document.getElementById("notify-switch")
|
||||
var notifySetting = localStorageGet("notify-api")
|
||||
var notifyPermissionExplained = 0; // 1 = granted msg shown, -1 = denied message shown
|
||||
|
||||
// Inital request for notifications permission
|
||||
// Initial request for notifications permission
|
||||
function RequestNotifyPermission() {
|
||||
try {
|
||||
var notifyPromise = Notification.requestPermission();
|
||||
|
@ -361,7 +389,9 @@ function join(channel) {
|
|||
var args = JSON.parse(message.data);
|
||||
var cmd = args.cmd;
|
||||
var command = COMMANDS[cmd];
|
||||
command.call(null, args);
|
||||
if (command) {
|
||||
command.call(null, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,7 +400,63 @@ var COMMANDS = {
|
|||
if (ignoredUsers.indexOf(args.nick) >= 0) {
|
||||
return;
|
||||
}
|
||||
pushMessage(args);
|
||||
|
||||
var elem = pushMessage(args);
|
||||
|
||||
if (typeof (args.customId) === 'string') {
|
||||
addActiveMessage(args.customId, args.userid, args.text, elem);
|
||||
}
|
||||
},
|
||||
|
||||
updateMessage: function (args) {
|
||||
var customId = args.customId;
|
||||
var mode = args.mode;
|
||||
|
||||
if (!mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
var message;
|
||||
for (var i = 0; i < activeMessages.length; i++) {
|
||||
var msg = activeMessages[i];
|
||||
if (msg.userid === args.userid && msg.customId === customId) {
|
||||
if (mode === 'complete') {
|
||||
activeMessages.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
message = msg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
var textElem = message.elem.querySelector('.text');
|
||||
if (!textElem) {
|
||||
return;
|
||||
}
|
||||
|
||||
var newText = message.text;
|
||||
if (mode === 'overwrite') {
|
||||
newText = args.text;
|
||||
} else if (mode === 'append') {
|
||||
newText += args.text;
|
||||
} else if (mode === 'prepend') {
|
||||
newText = args.text + newText;
|
||||
}
|
||||
|
||||
message.text = newText;
|
||||
|
||||
// Scroll to bottom if necessary
|
||||
var atBottom = isAtBottom();
|
||||
|
||||
textElem.innerHTML = md.render(newText);
|
||||
|
||||
if (atBottom) {
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
||||
},
|
||||
|
||||
info: function (args) {
|
||||
|
@ -378,6 +464,11 @@ var COMMANDS = {
|
|||
pushMessage(args);
|
||||
},
|
||||
|
||||
emote: function (args) {
|
||||
args.nick = '*';
|
||||
pushMessage(args);
|
||||
},
|
||||
|
||||
warn: function (args) {
|
||||
args.nick = '!';
|
||||
pushMessage(args);
|
||||
|
@ -413,6 +504,30 @@ var COMMANDS = {
|
|||
if ($('#joined-left').checked) {
|
||||
pushMessage({ nick: '*', text: nick + " left" });
|
||||
}
|
||||
},
|
||||
|
||||
captcha: function (args) {
|
||||
var messageEl = document.createElement('div');
|
||||
messageEl.classList.add('info');
|
||||
|
||||
|
||||
var nickSpanEl = document.createElement('span');
|
||||
nickSpanEl.classList.add('nick');
|
||||
messageEl.appendChild(nickSpanEl);
|
||||
|
||||
var nickLinkEl = document.createElement('a');
|
||||
nickLinkEl.textContent = '#';
|
||||
nickSpanEl.appendChild(nickLinkEl);
|
||||
|
||||
var textEl = document.createElement('pre');
|
||||
textEl.style.fontSize = '4px';
|
||||
textEl.classList.add('text');
|
||||
textEl.innerHTML = args.text;
|
||||
|
||||
messageEl.appendChild(textEl);
|
||||
$('#messages').appendChild(messageEl);
|
||||
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -450,7 +565,13 @@ function pushMessage(args) {
|
|||
|
||||
if (args.trip) {
|
||||
var tripEl = document.createElement('span');
|
||||
tripEl.textContent = args.trip + " ";
|
||||
|
||||
if (args.mod) {
|
||||
tripEl.textContent = String.fromCodePoint(11088) + " " + args.trip + " ";
|
||||
} else {
|
||||
tripEl.textContent = args.trip + " ";
|
||||
}
|
||||
|
||||
tripEl.classList.add('trip');
|
||||
nickSpanEl.appendChild(tripEl);
|
||||
}
|
||||
|
@ -459,6 +580,10 @@ function pushMessage(args) {
|
|||
var nickLinkEl = document.createElement('a');
|
||||
nickLinkEl.textContent = args.nick;
|
||||
|
||||
if (args.color && /(^[0-9A-F]{6}$)|(^[0-9A-F]{3}$)/i.test(args.color)) {
|
||||
nickLinkEl.setAttribute('style', 'color:#' + args.color + ' !important');
|
||||
}
|
||||
|
||||
nickLinkEl.onclick = function () {
|
||||
insertAtCursor("@" + args.nick + " ");
|
||||
$('#chatinput').focus();
|
||||
|
@ -485,6 +610,8 @@ function pushMessage(args) {
|
|||
|
||||
unread += 1;
|
||||
updateTitle();
|
||||
|
||||
return messageEl;
|
||||
}
|
||||
|
||||
function insertAtCursor(text) {
|
||||
|
@ -641,12 +768,23 @@ $('#chatinput').onkeydown = function (e) {
|
|||
}
|
||||
}
|
||||
|
||||
// from https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser
|
||||
function checkIsMobileOrTablet() {
|
||||
let check = false;
|
||||
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
|
||||
return check;
|
||||
};
|
||||
|
||||
var isMobileOrTablet = checkIsMobileOrTablet();
|
||||
|
||||
function updateInputSize() {
|
||||
var atBottom = isAtBottom();
|
||||
if (!isMobileOrTablet) {
|
||||
var input = $('#chatinput');
|
||||
input.style.height = 0;
|
||||
input.style.height = input.scrollHeight + 'px';
|
||||
}
|
||||
|
||||
var input = $('#chatinput');
|
||||
input.style.height = 0;
|
||||
input.style.height = input.scrollHeight + 'px';
|
||||
document.body.style.marginBottom = $('#footer').offsetHeight + 'px';
|
||||
|
||||
if (atBottom) {
|
||||
|
@ -672,8 +810,8 @@ $('#sidebar').onmouseleave = document.ontouchstart = function (event) {
|
|||
var e = event.toElement || event.relatedTarget;
|
||||
try {
|
||||
if (e.parentNode == this || e == this) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (e) { return; }
|
||||
|
||||
if (!$('#pin-sidebar').checked) {
|
||||
|
@ -701,8 +839,8 @@ if (localStorageGet('joined-left') == 'false') {
|
|||
|
||||
if (localStorageGet('parse-latex') == 'false') {
|
||||
$('#parse-latex').checked = false;
|
||||
md.inline.ruler.disable([ 'katex' ]);
|
||||
md.block.ruler.disable([ 'katex' ]);
|
||||
md.inline.ruler.disable(['katex']);
|
||||
md.block.ruler.disable(['katex']);
|
||||
}
|
||||
|
||||
$('#pin-sidebar').onchange = function (e) {
|
||||
|
@ -717,11 +855,11 @@ $('#parse-latex').onchange = function (e) {
|
|||
var enabled = !!e.target.checked;
|
||||
localStorageSet('parse-latex', enabled);
|
||||
if (enabled) {
|
||||
md.inline.ruler.enable([ 'katex' ]);
|
||||
md.block.ruler.enable([ 'katex' ]);
|
||||
md.inline.ruler.enable(['katex']);
|
||||
md.block.ruler.enable(['katex']);
|
||||
} else {
|
||||
md.inline.ruler.disable([ 'katex' ]);
|
||||
md.block.ruler.disable([ 'katex' ]);
|
||||
md.inline.ruler.disable(['katex']);
|
||||
md.block.ruler.disable(['katex']);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -830,15 +968,16 @@ var schemes = [
|
|||
'pop',
|
||||
'railscasts',
|
||||
'solarized',
|
||||
'tk-night',
|
||||
'tomorrow',
|
||||
'tk-night',
|
||||
'carrot',
|
||||
'lax',
|
||||
'Ubuntu',
|
||||
'gruvbox-light',
|
||||
'fried-egg',
|
||||
'rainbow',
|
||||
'amoled'
|
||||
'amoled',
|
||||
'retro'
|
||||
];
|
||||
|
||||
var highlights = [
|
||||
|
@ -892,7 +1031,7 @@ $('#highlight-selector').onchange = function (e) {
|
|||
setHighlight(e.target.value);
|
||||
}
|
||||
|
||||
// Load sidebar configaration values from local storage if available
|
||||
// Load sidebar configuration values from local storage if available
|
||||
if (localStorageGet('scheme')) {
|
||||
setScheme(localStorageGet('scheme'));
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<source src="audio/notify.mp3" type="audio/ogg">
|
||||
</audio>
|
||||
<article class="container">
|
||||
<div id="messages" class="messages"></div>
|
||||
<div id="messages"></div>
|
||||
</article>
|
||||
<footer id="footer">
|
||||
<div class="container">
|
||||
|
|
|
@ -221,10 +221,8 @@ ul ul, ol ol {
|
|||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.messages {
|
||||
border: none;
|
||||
}
|
||||
#messages {
|
||||
border: none;
|
||||
padding: 0.5em;
|
||||
}
|
||||
.message {
|
||||
|
|
108
commands/admin/addmod.js
Normal file
108
commands/admin/addmod.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Create a new mod trip
|
||||
* @version 1.0.0
|
||||
* @description Adds target trip to the config as a mod and upgrades the socket type
|
||||
* @module addmod
|
||||
*/
|
||||
|
||||
import {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
levels,
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (!isAdmin(socket.level)) {
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// add new trip to config
|
||||
core.appConfig.data.globalMods.push({ trip: payload.trip });
|
||||
|
||||
// find targets current connections
|
||||
const newMod = server.findSockets({ trip: payload.trip });
|
||||
if (newMod.length !== 0) {
|
||||
// build update notice with new privileges
|
||||
const updateNotice = {
|
||||
...getUserDetails(newMod[0]),
|
||||
...{
|
||||
cmd: 'updateUser',
|
||||
uType: 'mod', // @todo use legacyLevelToLabel from _LegacyFunctions.js
|
||||
level: levels.moderator,
|
||||
},
|
||||
};
|
||||
|
||||
for (let i = 0, l = newMod.length; i < l; i += 1) {
|
||||
// upgrade privileges
|
||||
newMod[i].uType = 'mod'; // @todo use legacyLevelToLabel from _LegacyFunctions.js
|
||||
newMod[i].level = levels.moderator;
|
||||
|
||||
// inform new mod
|
||||
server.send({
|
||||
cmd: 'info',
|
||||
text: 'You are now a mod.',
|
||||
channel: newMod[i].channel, // @todo Multichannel
|
||||
}, newMod[i]);
|
||||
|
||||
// notify channel
|
||||
server.broadcast({
|
||||
...updateNotice,
|
||||
...{
|
||||
channel: newMod[i].channel,
|
||||
},
|
||||
}, { channel: newMod[i].channel });
|
||||
}
|
||||
}
|
||||
|
||||
// return success message
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `Added mod trip: ${payload.trip}, remember to run 'saveconfig' to make it permanent`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
// notify all mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Added mod: ${payload.trip}`,
|
||||
channel: false, // @todo Multichannel, false for global info
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "trip"
|
||||
* @public
|
||||
* @typedef {Array} addmod/requiredData
|
||||
*/
|
||||
export const requiredData = ['trip'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} addmod/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'addmod',
|
||||
category: 'admin',
|
||||
description: 'Adds target trip to the config as a mod and upgrades the socket type',
|
||||
usage: `
|
||||
API: { cmd: 'addmod', trip: '<target trip>' }`,
|
||||
};
|
77
commands/admin/listusers.js
Normal file
77
commands/admin/listusers.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
/* eslint no-unused-vars: 0 */
|
||||
/* eslint no-restricted-syntax: 0 */
|
||||
/* eslint guard-for-in: 0 */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Show users and channels
|
||||
* @version 1.0.0
|
||||
* @description Outputs all current channels and sockets in those channels
|
||||
* @module listusers
|
||||
*/
|
||||
|
||||
import {
|
||||
isAdmin,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ server, socket }) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (!isAdmin(socket.level)) {
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// find all users currently in a channel
|
||||
const currentUsers = server.findSockets({
|
||||
channel: (channel) => true,
|
||||
});
|
||||
|
||||
// compile channel and user list
|
||||
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}`,
|
||||
);
|
||||
}
|
||||
|
||||
// build output
|
||||
const lines = [];
|
||||
for (const channel in channels) {
|
||||
lines.push(`?${channel} ${channels[channel].join(', ')}`);
|
||||
}
|
||||
|
||||
// send reply
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: lines.join('\n'),
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} listusers/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'listusers',
|
||||
category: 'admin',
|
||||
description: 'Outputs all current channels and sockets in those channels',
|
||||
usage: `
|
||||
API: { cmd: 'listusers' }`,
|
||||
};
|
71
commands/admin/reload.js
Normal file
71
commands/admin/reload.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Refresh modules
|
||||
* @version 1.0.0
|
||||
* @description Allows a remote user to clear and re-import the server command modules
|
||||
* @module reload
|
||||
*/
|
||||
|
||||
import {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (!isAdmin(socket.level)) {
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// do command reload and store results
|
||||
let loadResult = await core.commands.reloadCommands();
|
||||
|
||||
// clear and rebuild all module hooks
|
||||
server.loadHooks();
|
||||
|
||||
// build reply based on reload results
|
||||
if (loadResult === '') {
|
||||
loadResult = `Reloaded ${core.commands.commands.length} commands, 0 errors`;
|
||||
} else {
|
||||
loadResult = `Reloaded ${core.commands.commands.length} commands, error(s):
|
||||
${loadResult}`;
|
||||
}
|
||||
|
||||
if (typeof payload.reason !== 'undefined') {
|
||||
loadResult += `\nReason: ${payload.reason}`;
|
||||
}
|
||||
|
||||
// send results to moderators (which the user using this command is higher than)
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: loadResult,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} reload/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'reload',
|
||||
category: 'admin',
|
||||
description: 'Allows a remote user to clear and re-import the server command modules',
|
||||
usage: `
|
||||
API: { cmd: 'reload', reason: '<optional reason append>' }`,
|
||||
};
|
113
commands/admin/removemod.js
Normal file
113
commands/admin/removemod.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Removes a mod
|
||||
* @version 1.0.0
|
||||
* @description Removes target trip from the config as a mod and downgrades the socket type
|
||||
* @module removemod
|
||||
*/
|
||||
|
||||
import {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
levels,
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (!isAdmin(socket.level)) {
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// remove trip from config
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
core.appConfig.data.globalMods = core.appConfig.data.globalMods.filter(
|
||||
(mod) => mod.trip !== payload.trip,
|
||||
);
|
||||
|
||||
// find targets current connections
|
||||
const targetMod = server.findSockets({ trip: payload.trip });
|
||||
if (targetMod.length !== 0) {
|
||||
// build update notice with new privileges
|
||||
const updateNotice = {
|
||||
...getUserDetails(targetMod[0]),
|
||||
...{
|
||||
cmd: 'updateUser',
|
||||
uType: 'user', // @todo use legacyLevelToLabel from _LegacyFunctions.js
|
||||
level: levels.default,
|
||||
},
|
||||
};
|
||||
|
||||
for (let i = 0, l = targetMod.length; i < l; i += 1) {
|
||||
// downgrade privileges
|
||||
targetMod[i].uType = 'user';
|
||||
targetMod[i].level = levels.default;
|
||||
|
||||
// inform ex-mod
|
||||
server.send({
|
||||
cmd: 'info',
|
||||
text: 'You are now a user.',
|
||||
channel: targetMod[i].channel, // @todo Multichannel
|
||||
}, targetMod[i]);
|
||||
|
||||
// notify channel
|
||||
server.broadcast({
|
||||
...updateNotice,
|
||||
...{
|
||||
channel: targetMod[i].channel,
|
||||
},
|
||||
}, { channel: targetMod[i].channel });
|
||||
}
|
||||
}
|
||||
|
||||
// return success message
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `Removed mod trip: ${
|
||||
payload.trip
|
||||
}, remember to run 'saveconfig' to make it permanent`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
// notify all mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Removed mod: ${payload.trip}`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "trip"
|
||||
* @public
|
||||
* @typedef {Array} removemod/requiredData
|
||||
*/
|
||||
export const requiredData = ['trip'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} removemod/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'removemod',
|
||||
category: 'admin',
|
||||
description: 'Removes target trip from the config as a mod and downgrades the socket type',
|
||||
usage: `
|
||||
API: { cmd: 'removemod', trip: '<target trip>' }`,
|
||||
};
|
62
commands/admin/saveconfig.js
Normal file
62
commands/admin/saveconfig.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Saves the config
|
||||
* @version 1.0.0
|
||||
* @description Writes the current config to disk
|
||||
* @module saveconfig
|
||||
*/
|
||||
|
||||
import {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ core, server, socket }) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (!isAdmin(socket.level)) {
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// attempt save, notify of failure
|
||||
try {
|
||||
await core.appConfig.write();
|
||||
} catch (err) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Failed to save config, check logs.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// return success message to moderators and admins
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: 'Config saved!',
|
||||
channel: false, // @todo Multichannel
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} saveconfig/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'saveconfig',
|
||||
category: 'admin',
|
||||
description: 'Writes the current config to disk',
|
||||
usage: `
|
||||
API: { cmd: 'saveconfig' }`,
|
||||
};
|
58
commands/admin/shout.js
Normal file
58
commands/admin/shout.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Emit text everywhere
|
||||
* @version 1.0.0
|
||||
* @description Displays passed text to every client connected
|
||||
* @module shout
|
||||
*/
|
||||
|
||||
import {
|
||||
isAdmin,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ server, socket, payload }) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (!isAdmin(socket.level)) {
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// send text to all channels
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Server Notice: ${payload.text}`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, {});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "text"
|
||||
* @public
|
||||
* @typedef {Array} shout/requiredData
|
||||
*/
|
||||
export const requiredData = ['text'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} shout/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'shout',
|
||||
category: 'admin',
|
||||
description: 'Displays passed text to every client connected',
|
||||
usage: `
|
||||
API: { cmd: 'shout', text: '<shout text>' }`,
|
||||
};
|
158
commands/core/changecolor.js
Normal file
158
commands/core/changecolor.js
Normal file
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Update name color
|
||||
* @version 1.0.0
|
||||
* @description Allows calling client to change their nickname color
|
||||
* @module changecolor
|
||||
*/
|
||||
|
||||
import {
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Validate a string as a valid hex color string
|
||||
* @param {string} color - Color string to validate
|
||||
* @private
|
||||
* @todo Move into utility module
|
||||
* @return {boolean}
|
||||
*/
|
||||
const verifyColor = (color) => /(^[0-9A-F]{6}$)|(^[0-9A-F]{3}$)/i.test(color);
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
server, socket, payload,
|
||||
}) {
|
||||
const { channel } = socket;
|
||||
|
||||
if (server.police.frisk(socket, 1)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are changing colors too fast. Wait a moment before trying again.',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// verify user data is string
|
||||
if (typeof payload.color !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure requested nickname meets standards
|
||||
const newColor = payload.color.trim().toUpperCase().replace(/#/g, '');
|
||||
if (newColor !== 'RESET' && !verifyColor(newColor)) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Invalid color! Color must be in hex value',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
if (newColor === 'RESET') {
|
||||
socket.color = false; // eslint-disable-line no-param-reassign
|
||||
} else {
|
||||
socket.color = newColor; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
// build update notice with new color
|
||||
const updateNotice = {
|
||||
...getUserDetails(socket),
|
||||
...{
|
||||
cmd: 'updateUser',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
},
|
||||
};
|
||||
|
||||
// notify channel that the user has changed their name
|
||||
// @todo this should be sent to every channel the user is in (multichannel)
|
||||
server.broadcast(updateNotice, { channel: socket.channel });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.colorCheck.bind(this), 29);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function colorCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/color ')) {
|
||||
const input = payload.text.split(' ');
|
||||
|
||||
// If there is no color target parameter
|
||||
if (input[1] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Refer to `/help changecolor` for instructions on how to use this command.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'changecolor',
|
||||
color: input[1],
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "color"
|
||||
* @public
|
||||
* @typedef {Array} changecolor/requiredData
|
||||
*/
|
||||
export const requiredData = ['color'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} changecolor/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'changecolor',
|
||||
category: 'core',
|
||||
description: 'Allows calling client to change their nickname color',
|
||||
usage: `
|
||||
API: { cmd: 'changecolor', color: '<new color as hex>' }
|
||||
Text: /color <new color as hex>
|
||||
Removal: /color reset`,
|
||||
};
|
213
commands/core/changenick.js
Normal file
213
commands/core/changenick.js
Normal file
|
@ -0,0 +1,213 @@
|
|||
/* eslint eqeqeq: 0 */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Update nickname
|
||||
* @version 1.0.0
|
||||
* @description Allows calling client to change their current nickname
|
||||
* @module changenick
|
||||
*/
|
||||
|
||||
import {
|
||||
verifyNickname,
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
server, socket, payload,
|
||||
}) {
|
||||
const { channel } = socket;
|
||||
|
||||
if (server.police.frisk(socket, 6)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are changing nicknames too fast. Wait a moment before trying again.',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// verify user data is string
|
||||
if (typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const previousNick = socket.nick;
|
||||
|
||||
// make sure requested nickname meets standards
|
||||
const newNick = payload.nick.trim();
|
||||
if (!verifyNickname(newNick)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Nickname must consist of up to 24 letters, numbers, and underscores',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
if (newNick == previousNick) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You already have that name',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// find any sockets that have the same nickname
|
||||
const userExists = server.findSockets({
|
||||
channel,
|
||||
nick: (targetNick) => targetNick.toLowerCase() === newNick.toLowerCase()
|
||||
// Allow them to rename themselves to a different case
|
||||
&& targetNick != previousNick,
|
||||
});
|
||||
|
||||
// return error if found
|
||||
if (userExists.length > 0) {
|
||||
// That nickname is already in that channel
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Nickname taken',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// build update notice with new nickname
|
||||
const updateNotice = {
|
||||
...getUserDetails(socket),
|
||||
...{
|
||||
cmd: 'updateUser',
|
||||
nick: newNick,
|
||||
channel, // @todo Multichannel
|
||||
},
|
||||
};
|
||||
|
||||
// build join and leave notices for legacy clients
|
||||
const leaveNotice = {
|
||||
cmd: 'onlineRemove',
|
||||
userid: socket.userid,
|
||||
nick: socket.nick,
|
||||
channel, // @todo Multichannel
|
||||
};
|
||||
|
||||
const joinNotice = {
|
||||
...getUserDetails(socket),
|
||||
...{
|
||||
cmd: 'onlineAdd',
|
||||
nick: newNick,
|
||||
channel, // @todo Multichannel
|
||||
},
|
||||
};
|
||||
|
||||
// gather channel peers
|
||||
const peerList = server.findSockets({ channel });
|
||||
for (let i = 0, l = peerList.length; i < l; i += 1) {
|
||||
if (peerList[i].hcProtocol === 1) {
|
||||
// send join/leave to legacy clients
|
||||
server.send(leaveNotice, peerList[i]);
|
||||
server.send(joinNotice, peerList[i]);
|
||||
} else {
|
||||
// send update info
|
||||
// @todo this should be sent to every channel the client is in (multichannel)
|
||||
server.send(updateNotice, peerList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// notify channel that the user has changed their name
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick} is now ${newNick}`,
|
||||
channel, // @todo Multichannel
|
||||
}, { channel });
|
||||
|
||||
// commit change to nickname
|
||||
socket.nick = newNick; // eslint-disable-line no-param-reassign
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.nickCheck.bind(this), 29);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function nickCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/nick')) {
|
||||
const input = payload.text.split(' ');
|
||||
|
||||
// If there is no nickname target parameter
|
||||
if (!input[1]) {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Refer to `/help nick` for instructions on how to use this command.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const newNick = input[1].replace(/@/g, '');
|
||||
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'changenick',
|
||||
nick: newNick,
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "nick"
|
||||
* @public
|
||||
* @typedef {Array} changenick/requiredData
|
||||
*/
|
||||
export const requiredData = ['nick'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} changenick/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'changenick',
|
||||
category: 'core',
|
||||
description: 'Allows calling client to change their current nickname',
|
||||
usage: `
|
||||
API: { cmd: 'changenick', nick: '<new nickname>' }
|
||||
Text: /nick <new nickname>`,
|
||||
};
|
245
commands/core/chat.js
Normal file
245
commands/core/chat.js
Normal file
|
@ -0,0 +1,245 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Send chat messages
|
||||
* @version 1.0.0
|
||||
* @description Broadcasts passed `text` field to the calling users channel
|
||||
* @module chat
|
||||
*/
|
||||
|
||||
import {
|
||||
parseText,
|
||||
} from '../utility/_Text.js';
|
||||
import {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Maximum length of the customId property
|
||||
* @type {number}
|
||||
*/
|
||||
export const MAX_MESSAGE_ID_LENGTH = 6;
|
||||
|
||||
/**
|
||||
* The time in milliseconds before a message is considered stale, and thus no longer allowed
|
||||
* to be edited.
|
||||
* @type {number}
|
||||
*/
|
||||
const ACTIVE_TIMEOUT = 5 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* The time in milliseconds that a check for stale messages should be performed.
|
||||
* @type {number}
|
||||
*/
|
||||
const TIMEOUT_CHECK_INTERVAL = 30 * 1000;
|
||||
|
||||
/**
|
||||
* Stores active messages that can be edited.
|
||||
* @type {Array}
|
||||
*/
|
||||
export const ACTIVE_MESSAGES = [];
|
||||
|
||||
/**
|
||||
* Cleans up stale messages.
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function cleanActiveMessages() {
|
||||
const now = Date.now();
|
||||
for (let i = 0; i < ACTIVE_MESSAGES.length; i += 1) {
|
||||
const message = ACTIVE_MESSAGES[i];
|
||||
if (now - message.sent > ACTIVE_TIMEOUT || message.toDelete) {
|
||||
ACTIVE_MESSAGES.splice(i, 1);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This won't get cleared on module reload.
|
||||
setInterval(cleanActiveMessages, TIMEOUT_CHECK_INTERVAL);
|
||||
|
||||
/**
|
||||
* Adds a message to the active messages map.
|
||||
* @public
|
||||
* @param {string} id
|
||||
* @param {number} userid
|
||||
* @return {void}
|
||||
*/
|
||||
export function addActiveMessage(customId, userid) {
|
||||
ACTIVE_MESSAGES.push({
|
||||
customId,
|
||||
userid,
|
||||
sent: Date.now(),
|
||||
toDelete: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// check user input
|
||||
const text = parseText(payload.text);
|
||||
|
||||
if (!text) {
|
||||
// lets not send objects or empty text, yea?
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
// check for spam
|
||||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket, score)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
const { customId } = payload;
|
||||
|
||||
if (typeof (customId) === 'string' && customId.length > MAX_MESSAGE_ID_LENGTH) {
|
||||
// There's a limit on the custom id length.
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
// build chat payload
|
||||
const outgoingPayload = {
|
||||
cmd: 'chat',
|
||||
nick: socket.nick, /* @legacy */
|
||||
uType: socket.uType, /* @legacy */
|
||||
userid: socket.userid,
|
||||
channel: socket.channel,
|
||||
text,
|
||||
level: socket.level,
|
||||
customId,
|
||||
};
|
||||
|
||||
if (isAdmin(socket.level)) {
|
||||
outgoingPayload.admin = true;
|
||||
} else if (isModerator(socket.level)) {
|
||||
outgoingPayload.mod = true;
|
||||
}
|
||||
|
||||
if (socket.trip) {
|
||||
outgoingPayload.trip = socket.trip; /* @legacy */
|
||||
}
|
||||
|
||||
if (socket.color) {
|
||||
outgoingPayload.color = socket.color;
|
||||
}
|
||||
|
||||
addActiveMessage(outgoingPayload.customId, socket.userid);
|
||||
|
||||
// broadcast to channel peers
|
||||
server.broadcast(outgoingPayload, { channel: socket.channel });
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('messages-sent');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.commandCheckIn.bind(this), 20);
|
||||
server.registerHook('in', 'chat', this.finalCmdCheck.bind(this), 254);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* checks for miscellaneous '/' based commands
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function commandCheckIn({ server, socket, payload }) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/myhash')) {
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `Your hash: ${socket.hash}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* assumes a failed chat command invocation and will reject with notice
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function finalCmdCheck({ server, socket, payload }) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!payload.text.startsWith('/')) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('//')) {
|
||||
payload.text = payload.text.substr(1); // eslint-disable-line no-param-reassign
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: `Unknown command: ${payload.text}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "text"
|
||||
* @public
|
||||
* @typedef {Array} chat/requiredData
|
||||
*/
|
||||
export const requiredData = ['text'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} chat/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'chat',
|
||||
category: 'core',
|
||||
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`,
|
||||
};
|
162
commands/core/emote.js
Normal file
162
commands/core/emote.js
Normal file
|
@ -0,0 +1,162 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Emote / action text
|
||||
* @version 1.0.0
|
||||
* @description Broadcasts an emote to the current channel
|
||||
* @module emote
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check and trim string provided by remote client
|
||||
* @param {string} text - Subject string
|
||||
* @private
|
||||
* @todo Move into utility module
|
||||
* @return {string|boolean}
|
||||
*/
|
||||
const parseText = (text) => {
|
||||
// verifies user input is text
|
||||
if (typeof text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let sanitizedText = 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ server, socket, payload }) {
|
||||
// check user input
|
||||
let text = parseText(payload.text);
|
||||
|
||||
if (!text) {
|
||||
// lets not send objects or empty text, yea?
|
||||
return server.police.frisk(socket, 8);
|
||||
}
|
||||
|
||||
// check for spam
|
||||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket, score)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
if (!text.startsWith("'")) {
|
||||
text = ` ${text}`;
|
||||
}
|
||||
|
||||
const newPayload = {
|
||||
cmd: 'emote',
|
||||
nick: socket.nick,
|
||||
userid: socket.userid,
|
||||
text: `@${socket.nick}${text}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
};
|
||||
|
||||
if (socket.trip) {
|
||||
newPayload.trip = socket.trip;
|
||||
}
|
||||
|
||||
// broadcast to channel peers
|
||||
server.broadcast(newPayload, { channel: socket.channel });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.emoteCheck.bind(this), 30);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hooks chat commands checking for /me
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function emoteCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/me ')) {
|
||||
const input = payload.text.split(' ');
|
||||
|
||||
// If there is no emote target parameter
|
||||
if (input[1] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Refer to `/help emote` for instructions on how to use this command.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
input.splice(0, 1);
|
||||
const actionText = input.join(' ');
|
||||
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'emote',
|
||||
text: actionText,
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "text"
|
||||
* @public
|
||||
* @typedef {Array} emote/requiredData
|
||||
*/
|
||||
export const requiredData = ['text'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} emote/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'emote',
|
||||
category: 'core',
|
||||
description: 'Broadcasts an emote to the current channel',
|
||||
usage: `
|
||||
API: { cmd: 'emote', text: '<emote/action text>' }
|
||||
Text: /me <emote/action text>`,
|
||||
};
|
|
@ -1,14 +1,26 @@
|
|||
/*
|
||||
Description: Outputs the current command module list or command categories
|
||||
*/
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Get help
|
||||
* @version 1.0.0
|
||||
* @description Outputs information about the servers current protocol
|
||||
* @module help
|
||||
*/
|
||||
|
||||
// module main
|
||||
export async function run(core, server, socket, payload) {
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// check for spam
|
||||
if (server.police.frisk(socket.address, 2)) {
|
||||
if (server.police.frisk(socket, 2)) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
|
@ -24,7 +36,9 @@ export async function run(core, server, socket, payload) {
|
|||
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())}:|`;
|
||||
const catCommands = core.commands.all(categories[i]).sort((a, b) => a.info.name.localeCompare(b.info.name));
|
||||
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`;
|
||||
}
|
||||
|
||||
|
@ -37,9 +51,11 @@ export async function run(core, server, socket, payload) {
|
|||
} else {
|
||||
reply += `# ${command.info.name} command:\n| | |\n|---:|---|\n`;
|
||||
reply += `|**Name:**|${command.info.name}|\n`;
|
||||
reply += `|**Hash:**|${command.info.srcHash}|\n`;
|
||||
reply += `|**Aliases:**|${typeof command.info.aliases !== 'undefined' ? command.info.aliases.join(', ') : 'None'}|\n`;
|
||||
reply += `|**Category:**|${command.info.category.replace('../src/commands/', '').replace(/^\w/, (c) => c.toUpperCase())}|\n`;
|
||||
reply += `|**Required Parameters:**|${command.requiredData || 'None'}|\n`;
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
reply += `|**Description:**|${command.info.description || '¯\_(ツ)_/¯'}|\n\n`;
|
||||
reply += `**Usage:** ${command.info.usage || command.info.name}`;
|
||||
}
|
||||
|
@ -49,18 +65,34 @@ export async function run(core, server, socket, payload) {
|
|||
server.reply({
|
||||
cmd: 'info',
|
||||
text: reply,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// module hook functions
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.helpCheck.bind(this), 28);
|
||||
}
|
||||
|
||||
// hooks chat commands checking for /whisper
|
||||
export function helpCheck(core, server, socket, payload) {
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hooks chat commands checking for /help
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function helpCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
@ -68,9 +100,14 @@ export function helpCheck(core, server, socket, payload) {
|
|||
if (payload.text.startsWith('/help')) {
|
||||
const input = payload.text.substr(1).split(' ', 2);
|
||||
|
||||
this.run(core, server, socket, {
|
||||
cmd: input[0],
|
||||
command: input[1],
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: input[0],
|
||||
command: input[1],
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
|
@ -79,8 +116,18 @@ export function helpCheck(core, server, socket, payload) {
|
|||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} help/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'help',
|
||||
category: 'core',
|
||||
description: 'Outputs information about the servers current protocol',
|
||||
usage: `
|
||||
API: { cmd: 'help', command: '<optional command name>' }
|
123
commands/core/invite.js
Normal file
123
commands/core/invite.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Send an invite
|
||||
* @version 1.0.0
|
||||
* @description Sends an invite to the target client with the provided channel, or a random channel
|
||||
* @module invite
|
||||
*/
|
||||
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
legacyInviteOut,
|
||||
legacyInviteReply,
|
||||
} from '../utility/_LegacyFunctions.js';
|
||||
|
||||
/**
|
||||
* Returns the channel that should be invited to.
|
||||
* @param {any} channel
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
export function getChannel(channel = undefined) {
|
||||
if (typeof channel === 'string') {
|
||||
return channel;
|
||||
}
|
||||
return Math.random().toString(36).substr(2, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// check for spam
|
||||
if (server.police.frisk(socket, 2)) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You are sending invites too fast. Wait a moment before trying again.',
|
||||
id: Errors.Invite.RATELIMIT,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// verify user input
|
||||
// if this is a legacy client add missing params to payload
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof socket.channel === 'undefined' || typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
} else if (typeof payload.userid !== 'number' || typeof payload.channel !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// @todo Verify this socket is part of payload.channel - multichannel patch
|
||||
// find target user
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// generate common channel
|
||||
const channel = getChannel(payload.to);
|
||||
|
||||
// build invite
|
||||
const outgoingPayload = {
|
||||
cmd: 'invite',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
from: socket.userid,
|
||||
to: targetUser.userid,
|
||||
inviteChannel: channel,
|
||||
};
|
||||
|
||||
// send invite notice to target client
|
||||
if (targetUser.hcProtocol === 1) {
|
||||
server.reply(legacyInviteOut(outgoingPayload, socket.nick), targetUser);
|
||||
} else {
|
||||
server.reply(outgoingPayload, targetUser);
|
||||
}
|
||||
|
||||
// send invite notice to this client
|
||||
if (socket.hcProtocol === 1) {
|
||||
server.reply(legacyInviteReply(outgoingPayload, targetUser.nick), socket);
|
||||
} else {
|
||||
server.reply(outgoingPayload, socket);
|
||||
}
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('invites-sent');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} invite/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'invite',
|
||||
category: 'core',
|
||||
description: 'Sends an invite to the target client with the provided channel, or a random channel.',
|
||||
usage: `
|
||||
API: { cmd: 'invite', nick: '<target nickname>', to: '<optional destination channel>' }`,
|
||||
};
|
278
commands/core/join.js
Normal file
278
commands/core/join.js
Normal file
|
@ -0,0 +1,278 @@
|
|||
/* eslint no-param-reassign: 0 */
|
||||
/* eslint import/no-cycle: [0, { ignoreExternal: true }] */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Join target channel
|
||||
* @version 1.0.0
|
||||
* @description Join the target channel using the supplied nick and password
|
||||
* @module join
|
||||
*/
|
||||
|
||||
import {
|
||||
getSession,
|
||||
} from './session.js';
|
||||
import {
|
||||
canJoinChannel,
|
||||
socketInChannel,
|
||||
} from '../utility/_Channels.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
upgradeLegacyJoin,
|
||||
legacyLevelToLabel,
|
||||
} from '../utility/_LegacyFunctions.js';
|
||||
import {
|
||||
verifyNickname,
|
||||
getUserPerms,
|
||||
getUserDetails,
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// check for spam
|
||||
if (server.police.frisk(socket, 3)) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You are joining channels too fast. Wait a moment and try again.',
|
||||
id: Errors.Join.RATELIMIT,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// `join` is the legacy entry point, check if it needs to be upgraded
|
||||
if (typeof socket.hcProtocol === 'undefined' || socket.hcProtocol === 1) {
|
||||
payload = upgradeLegacyJoin(server, socket, payload);
|
||||
}
|
||||
|
||||
// store payload values
|
||||
const { channel, nick, pass } = payload;
|
||||
|
||||
// check if a client is able to join target channel
|
||||
const mayJoin = canJoinChannel(channel, socket);
|
||||
if (mayJoin !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You may not join that channel.',
|
||||
id: mayJoin,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// calling socket already in a channel
|
||||
// @todo multichannel update, will remove
|
||||
if (typeof socket.channel !== 'undefined') {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove this
|
||||
text: 'Joining more than one channel is not currently supported',
|
||||
id: Errors.Join.ALREADY_JOINED,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
// end todo
|
||||
|
||||
// validates the user input for `nick`
|
||||
if (verifyNickname(nick, socket) !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Nickname must consist of up to 24 letters, numbers, and underscores',
|
||||
id: Errors.Join.INVALID_NICK,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// get trip and level
|
||||
const { trip, level } = getUserPerms(pass, core.saltKey, core.appConfig.data, channel);
|
||||
|
||||
// store the user values
|
||||
const userInfo = {
|
||||
nick,
|
||||
trip,
|
||||
uType: legacyLevelToLabel(level),
|
||||
hash: socket.hash,
|
||||
level,
|
||||
userid: socket.userid,
|
||||
isBot: socket.isBot,
|
||||
color: socket.color,
|
||||
channel,
|
||||
};
|
||||
|
||||
// check if the nickname already exists in the channel
|
||||
const userExists = server.findSockets({
|
||||
channel,
|
||||
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',
|
||||
id: Errors.Join.NAME_TAKEN,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// prepare to notify channel peers
|
||||
const newPeerList = server.findSockets({ channel });
|
||||
const nicks = []; /* @legacy */
|
||||
const users = [];
|
||||
const joinAnnouncement = { ...{ cmd: 'onlineAdd' }, ...userInfo };
|
||||
|
||||
// send join announcement and prep online set reply
|
||||
for (let i = 0, l = newPeerList.length; i < l; i += 1) {
|
||||
server.reply(joinAnnouncement, newPeerList[i]);
|
||||
|
||||
nicks.push(newPeerList[i].nick); /* @legacy */
|
||||
users.push({
|
||||
...{
|
||||
channel,
|
||||
isme: false,
|
||||
},
|
||||
...getUserDetails(newPeerList[i]),
|
||||
});
|
||||
}
|
||||
|
||||
// store user info
|
||||
socket.nick = userInfo.nick;
|
||||
socket.trip = userInfo.trip;
|
||||
socket.level = userInfo.level;
|
||||
socket.uType = userInfo.uType; /* @legacy */
|
||||
socket.channel = channel; /* @legacy */
|
||||
// @todo multi-channel patch
|
||||
// socket.channels.push(channel);
|
||||
socket.channels = [channel];
|
||||
|
||||
// global mod perks
|
||||
if (isModerator(socket.level)) {
|
||||
socket.ratelimitImmune = true;
|
||||
}
|
||||
|
||||
nicks.push(userInfo.nick); /* @legacy */
|
||||
users.push({ ...{ isme: true, isBot: socket.isBot }, ...userInfo });
|
||||
|
||||
// reply with channel peer list
|
||||
server.reply({
|
||||
cmd: 'onlineSet',
|
||||
nicks, /* @legacy */
|
||||
users,
|
||||
channel, // @todo Multichannel (?)
|
||||
}, socket);
|
||||
|
||||
// update client with new session info
|
||||
server.reply({
|
||||
cmd: 'session',
|
||||
restored: false,
|
||||
token: getSession(socket, core),
|
||||
channels: socket.channels,
|
||||
}, socket);
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('users-joined');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function restoreJoin({
|
||||
server, socket, channel,
|
||||
}) {
|
||||
// check if a client is able to join target channel
|
||||
const mayJoin = canJoinChannel(channel, socket);
|
||||
if (mayJoin !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You may not join that channel.',
|
||||
id: mayJoin,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// store the user values
|
||||
const userInfo = {
|
||||
nick: socket.nick,
|
||||
trip: socket.trip,
|
||||
uType: legacyLevelToLabel(socket.level),
|
||||
hash: socket.hash,
|
||||
level: socket.level,
|
||||
userid: socket.userid,
|
||||
isBot: socket.isBot,
|
||||
color: socket.color,
|
||||
channel,
|
||||
};
|
||||
|
||||
// prepare to notify channel peers
|
||||
const newPeerList = server.findSockets({ channel });
|
||||
const nicks = []; /* @legacy */
|
||||
const users = [];
|
||||
const joinAnnouncement = { ...{ cmd: 'onlineAdd' }, ...userInfo };
|
||||
// build update notice with new privileges
|
||||
const updateAnnouncement = {
|
||||
...getUserDetails(socket),
|
||||
...{
|
||||
cmd: 'updateUser',
|
||||
online: true,
|
||||
},
|
||||
};
|
||||
|
||||
const isDuplicate = socketInChannel(server, channel, socket);
|
||||
|
||||
// send join announcement and prep online set reply
|
||||
for (let i = 0, l = newPeerList.length; i < l; i += 1) {
|
||||
if (isDuplicate) {
|
||||
server.reply(updateAnnouncement, newPeerList[i]);
|
||||
} else {
|
||||
server.reply(joinAnnouncement, newPeerList[i]);
|
||||
}
|
||||
|
||||
nicks.push(newPeerList[i].nick); /* @legacy */
|
||||
users.push({
|
||||
...{
|
||||
channel,
|
||||
isme: false,
|
||||
},
|
||||
...getUserDetails(newPeerList[i]),
|
||||
});
|
||||
}
|
||||
|
||||
nicks.push(userInfo.nick); /* @legacy */
|
||||
users.push({ ...{ isme: true, isBot: socket.isBot }, ...userInfo });
|
||||
|
||||
// reply with channel peer list
|
||||
server.reply({
|
||||
cmd: 'onlineSet',
|
||||
nicks, /* @legacy */
|
||||
users,
|
||||
channel, // @todo Multichannel (?)
|
||||
}, socket);
|
||||
|
||||
socket.channel = channel; /* @legacy */
|
||||
socket.channels.push(channel);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} join/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'join',
|
||||
category: 'core',
|
||||
description: 'Join the target channel using the supplied nick and password',
|
||||
usage: `
|
||||
API: { cmd: 'join', nick: '<your nickname>', pass: '<optional password>', channel: '<target channel>' }`,
|
||||
};
|
164
commands/core/morestats.js
Normal file
164
commands/core/morestats.js
Normal file
|
@ -0,0 +1,164 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Get stats
|
||||
* @version 1.0.0
|
||||
* @description Sends back current server stats to the calling client
|
||||
* @module morestats
|
||||
*/
|
||||
|
||||
/**
|
||||
* Format input time into string
|
||||
* @param {Date} time - Subject date
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
const formatTime = (time) => {
|
||||
let seconds = time[0] + time[1] / 1e9;
|
||||
|
||||
let minutes = Math.floor(seconds / 60);
|
||||
seconds %= 60;
|
||||
|
||||
let hours = Math.floor(minutes / 60);
|
||||
minutes %= 60;
|
||||
|
||||
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`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ core, server, socket }) {
|
||||
// gather connection and channel count
|
||||
const ips = {};
|
||||
const channels = {};
|
||||
// @todo use public channel flag
|
||||
const publicChanCounts = {
|
||||
lounge: 0,
|
||||
meta: 0,
|
||||
math: 0,
|
||||
physics: 0,
|
||||
chemistry: 0,
|
||||
technology: 0,
|
||||
programming: 0,
|
||||
games: 0,
|
||||
banana: 0,
|
||||
chinese: 0,
|
||||
};
|
||||
|
||||
// @todo code resuage between here and `session`; should share exported function
|
||||
server.clients.forEach((client) => {
|
||||
if (client.channel) {
|
||||
channels[client.channel] = true;
|
||||
ips[client.address] = true;
|
||||
if (typeof publicChanCounts[client.channel] !== 'undefined') {
|
||||
publicChanCounts[client.channel] += 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const uniqueClientCount = Object.keys(ips).length;
|
||||
const uniqueChannels = Object.keys(channels).length;
|
||||
const joins = core.stats.get('users-joined') || 0;
|
||||
const invites = core.stats.get('invites-sent') || 0;
|
||||
const messages = core.stats.get('messages-sent') || 0;
|
||||
const banned = core.stats.get('users-banned') || 0;
|
||||
const kicked = core.stats.get('users-kicked') || 0;
|
||||
const stats = core.stats.get('stats-requested') || 0;
|
||||
const uptime = formatTime(process.hrtime(core.stats.get('start-time')));
|
||||
|
||||
// dispatch info
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
users: uniqueClientCount,
|
||||
chans: uniqueChannels,
|
||||
joins,
|
||||
invites,
|
||||
messages,
|
||||
banned,
|
||||
kicked,
|
||||
stats,
|
||||
uptime,
|
||||
public: publicChanCounts,
|
||||
text: `current-connections: ${uniqueClientCount}
|
||||
current-channels: ${uniqueChannels}
|
||||
users-joined: ${joins}
|
||||
invites-sent: ${invites}
|
||||
messages-sent: ${messages}
|
||||
users-banned: ${banned}
|
||||
users-kicked: ${kicked}
|
||||
stats-requested: ${stats}
|
||||
server-uptime: ${uptime}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('stats-requested');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.statsCheck.bind(this), 26);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hooks chat commands checking for /stats
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function statsCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/stats')) {
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'morestats',
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} morestats/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'morestats',
|
||||
category: 'core',
|
||||
description: 'Sends back current server stats to the calling client',
|
||||
usage: `
|
||||
API: { cmd: 'morestats' }
|
||||
Text: /stats`,
|
||||
};
|
32
commands/core/ping.js
Normal file
32
commands/core/ping.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
/* eslint no-empty-function: 0 */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Legacy support module
|
||||
* @version 1.0.0
|
||||
* @description This module is only in place to supress error notices legacy clients may get
|
||||
* @module ping
|
||||
*/
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run() { }
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} ping/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'ping',
|
||||
category: 'core',
|
||||
description: 'This module is only in place to supress error notices legacy clients may get',
|
||||
usage: 'none',
|
||||
};
|
199
commands/core/session.js
Normal file
199
commands/core/session.js
Normal file
|
@ -0,0 +1,199 @@
|
|||
/* eslint import/no-cycle: [0, { ignoreExternal: true }] */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Create or restore session
|
||||
* @version 1.0.0
|
||||
* @description Restore previous state by session or create new session
|
||||
* @module session
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
verifyNickname,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
restoreJoin,
|
||||
} from './join.js';
|
||||
|
||||
const SessionLocation = './session.key';
|
||||
|
||||
/**
|
||||
* Get a new json web token for the provided socket
|
||||
* @param {*} socket
|
||||
* @param {*} core
|
||||
* @returns {object}
|
||||
*/
|
||||
export function getSession(socket, core) {
|
||||
return jsonwebtoken.sign({
|
||||
channel: socket.channel,
|
||||
channels: socket.channels,
|
||||
color: socket.color,
|
||||
isBot: socket.isBot,
|
||||
level: socket.level,
|
||||
nick: socket.nick,
|
||||
trip: socket.trip,
|
||||
userid: socket.userid,
|
||||
uType: socket.uType,
|
||||
muzzled: socket.muzzled || false,
|
||||
banned: socket.banned || false,
|
||||
}, core.sessionKey, {
|
||||
expiresIn: '7 days',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply to target socket with session failure notice
|
||||
* @param {*} server
|
||||
* @param {*} socket
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function notifyFailure(server, socket) {
|
||||
server.reply({
|
||||
cmd: 'error',
|
||||
id: Errors.Session.BAD_SESSION,
|
||||
text: 'Invalid session',
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.token === 'undefined') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
let session = false;
|
||||
try {
|
||||
session = jsonwebtoken.verify(payload.token, core.sessionKey);
|
||||
} catch (err) {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
// validate session
|
||||
if (typeof session.channel !== 'string') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (Array.isArray(session.channels) === false) {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.color !== 'string' && typeof session.color !== 'boolean') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.isBot !== 'boolean') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.level !== 'number') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (verifyNickname(session.nick) === false) {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.trip !== 'string') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.userid !== 'number') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.uType !== 'string') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.muzzled !== 'boolean') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.banned !== 'boolean') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
// populate socket info with validated session
|
||||
socket.channels = [];
|
||||
socket.color = session.color;
|
||||
socket.isBot = session.isBot;
|
||||
socket.level = session.level;
|
||||
socket.nick = session.nick;
|
||||
socket.trip = session.trip;
|
||||
socket.userid = session.userid;
|
||||
socket.uType = session.uType;
|
||||
socket.muzzled = session.muzzled;
|
||||
socket.banned = session.banned;
|
||||
|
||||
// global mod perks
|
||||
if (isModerator(socket.level)) {
|
||||
socket.ratelimitImmune = true;
|
||||
}
|
||||
|
||||
socket.hash = server.getSocketHash(socket);
|
||||
socket.hcProtocol = 2;
|
||||
|
||||
// dispatch info
|
||||
server.reply({
|
||||
cmd: 'session',
|
||||
restored: true,
|
||||
token: getSession(socket, core),
|
||||
channels: socket.channels,
|
||||
}, socket);
|
||||
|
||||
for (let i = 0, j = session.channels.length; i < j; i += 1) {
|
||||
restoreJoin({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
channel: session.channels[i],
|
||||
}, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready
|
||||
* @param {Object} core - Reference to core enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function init(core) {
|
||||
// load the encryption key if required
|
||||
if (typeof core.sessionKey === 'undefined') {
|
||||
core.sessionKey = fs.readFileSync(SessionLocation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} session/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'session',
|
||||
category: 'core',
|
||||
description: 'Restore previous state by session or create new session',
|
||||
usage: "API: { cmd: 'session', id: '<previous session>' }",
|
||||
};
|
59
commands/core/stats.js
Normal file
59
commands/core/stats.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Simple stats
|
||||
* @version 1.0.0
|
||||
* @description Sends back legacy server stats to the calling client
|
||||
* @module stats
|
||||
*/
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ core, server, socket }) {
|
||||
// gather connection and channel count
|
||||
let ips = {};
|
||||
let channels = {};
|
||||
// for (const client of server.clients) {
|
||||
server.clients.forEach((client) => {
|
||||
if (client.channel) {
|
||||
channels[client.channel] = true;
|
||||
ips[client.address] = true;
|
||||
}
|
||||
});
|
||||
|
||||
const uniqueClientCount = Object.keys(ips).length;
|
||||
const uniqueChannels = Object.keys(channels).length;
|
||||
|
||||
ips = null;
|
||||
channels = null;
|
||||
|
||||
// dispatch info
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `${uniqueClientCount} unique IPs in ${uniqueChannels} channels`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('stats-requested');
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} stats/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'stats',
|
||||
category: 'core',
|
||||
description: 'Sends back legacy server stats to the calling client',
|
||||
usage: `
|
||||
API: { cmd: 'stats' }`,
|
||||
};
|
127
commands/core/updateMessage.js
Normal file
127
commands/core/updateMessage.js
Normal file
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* @author MinusGix ( https://github.com/MinusGix )
|
||||
* @summary Change target message
|
||||
* @version v1.0.0
|
||||
* @description Will alter a previously sent message using that message's customId
|
||||
* @module updateMessage
|
||||
*/
|
||||
|
||||
import {
|
||||
parseText,
|
||||
} from '../utility/_Text.js';
|
||||
import {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
ACTIVE_MESSAGES,
|
||||
MAX_MESSAGE_ID_LENGTH,
|
||||
} from './chat.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
server, socket, payload,
|
||||
}) {
|
||||
// undefined | "overwrite" | "append" | "prepend" | "complete"
|
||||
const { customId } = payload;
|
||||
let { mode, text } = payload;
|
||||
|
||||
if (!mode) {
|
||||
mode = 'overwrite';
|
||||
}
|
||||
|
||||
if (mode !== 'overwrite' && mode !== 'append' && mode !== 'prepend' && mode !== 'complete') {
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
if (!customId || typeof customId !== 'string' || customId.length > MAX_MESSAGE_ID_LENGTH) {
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
if (typeof (text) !== 'string') {
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
if (mode === 'overwrite') {
|
||||
text = parseText(text);
|
||||
|
||||
if (text === '') {
|
||||
text = '\u0000';
|
||||
}
|
||||
}
|
||||
|
||||
if (!text) {
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
// TODO: What score should we use for this? It isn't as space filling as chat messages.
|
||||
// But we also don't want a massive growing message.
|
||||
// Or flashing between huge and small. Etc.
|
||||
|
||||
let message;
|
||||
for (let i = 0; i < ACTIVE_MESSAGES.length; i += 1) {
|
||||
const msg = ACTIVE_MESSAGES[i];
|
||||
|
||||
if (msg.userid === socket.userid && msg.customId === customId) {
|
||||
message = ACTIVE_MESSAGES[i];
|
||||
if (mode === 'complete') {
|
||||
ACTIVE_MESSAGES[i].toDelete = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
return server.police.frisk(socket, 6);
|
||||
}
|
||||
|
||||
const outgoingPayload = {
|
||||
cmd: 'updateMessage',
|
||||
userid: socket.userid,
|
||||
channel: socket.channel,
|
||||
level: socket.level,
|
||||
mode,
|
||||
text,
|
||||
customId: message.customId,
|
||||
};
|
||||
|
||||
if (isAdmin(socket.level)) {
|
||||
outgoingPayload.admin = true;
|
||||
} else if (isModerator(socket.level)) {
|
||||
outgoingPayload.mod = true;
|
||||
}
|
||||
|
||||
server.broadcast(outgoingPayload, { channel: socket.channel });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "text", "customId"
|
||||
* @public
|
||||
* @typedef {Array} addmod/requiredData
|
||||
*/
|
||||
export const requiredData = ['text', 'customId'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} updateMessage/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'updateMessage',
|
||||
category: 'core',
|
||||
description: 'Update a message you have sent.',
|
||||
usage: `
|
||||
API: { cmd: 'updateMessage', mode: 'overwrite'|'append'|'prepend'|'complete', text: '<text to apply>', customId: '<customId sent with the chat message>' }`,
|
||||
};
|
231
commands/core/whisper.js
Normal file
231
commands/core/whisper.js
Normal file
|
@ -0,0 +1,231 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Send whisper
|
||||
* @version 1.0.0
|
||||
* @description Display text on target users screen that only they can see
|
||||
* @module whisper
|
||||
* @todo This should be changed to it's own event type, instead of `info`
|
||||
and accept a `userid` rather than `nick`
|
||||
*/
|
||||
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
legacyWhisperOut,
|
||||
legacyWhisperReply,
|
||||
} from '../utility/_LegacyFunctions.js';
|
||||
|
||||
/**
|
||||
* Check and trim string provided by remote client
|
||||
* @param {string} text - Subject string
|
||||
* @private
|
||||
* @todo Move into utility module
|
||||
* @return {string|boolean}
|
||||
*/
|
||||
const parseText = (text) => {
|
||||
// verifies user input is text
|
||||
if (typeof text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let sanitizedText = 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ server, socket, payload }) {
|
||||
// if this is a legacy client add missing params to payload
|
||||
if (socket.hcProtocol === 1) {
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
// verify user input
|
||||
const text = parseText(payload.text);
|
||||
|
||||
if (!text) {
|
||||
// lets not send objects or empty text, yea?
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
// check for spam
|
||||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket, score)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
const outgoingPayload = {
|
||||
cmd: 'whisper',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
from: socket.userid,
|
||||
to: targetUser.userid,
|
||||
text,
|
||||
};
|
||||
|
||||
// send invite notice to target client
|
||||
if (targetUser.hcProtocol === 1) {
|
||||
server.reply(legacyWhisperOut(outgoingPayload, socket), targetUser);
|
||||
} else {
|
||||
server.reply(outgoingPayload, targetUser);
|
||||
}
|
||||
|
||||
// send invite notice to this client
|
||||
if (socket.hcProtocol === 1) {
|
||||
server.reply(legacyWhisperReply(outgoingPayload, targetUser.nick), socket);
|
||||
} else {
|
||||
server.reply(outgoingPayload, socket);
|
||||
}
|
||||
|
||||
targetUser.whisperReply = socket.nick;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.whisperCheck.bind(this), 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hooks chat commands checking for /whisper
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function whisperCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/whisper ') || payload.text.startsWith('/w ')) {
|
||||
const input = payload.text.split(' ');
|
||||
|
||||
// If there is no nickname target parameter
|
||||
if (!input[1]) {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Refer to `/help whisper` for instructions on how to use this command.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = input[1].replace(/@/g, '');
|
||||
input.splice(0, 2);
|
||||
const whisperText = input.join(' ');
|
||||
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'whisper',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
nick: target,
|
||||
text: whisperText,
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/reply ') || payload.text.startsWith('/r ')) {
|
||||
if (typeof socket.whisperReply === 'undefined') {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Cannot reply to nobody',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const input = payload.text.split(' ');
|
||||
input.splice(0, 1);
|
||||
const whisperText = input.join(' ');
|
||||
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'whisper',
|
||||
nick: socket.whisperReply,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
text: whisperText,
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "nick", "text"
|
||||
* @public
|
||||
* @typedef {Array} whisper/requiredData
|
||||
*/
|
||||
export const requiredData = ['nick', 'text'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} whisper/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'whisper',
|
||||
category: 'core',
|
||||
description: 'Display text on target users screen that only they can see',
|
||||
usage: `
|
||||
API: { cmd: 'whisper', nick: '<target name>', text: '<text to whisper>' }
|
||||
Text: /whisper <target name> <text to whisper>
|
||||
Text: /w <target name> <text to whisper>
|
||||
Alt Text: /reply <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>`,
|
||||
};
|
66
commands/internal/disconnect.js
Normal file
66
commands/internal/disconnect.js
Normal file
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Disconnection handler
|
||||
* @version 1.0.0
|
||||
* @description The server invokes this module each time a websocket connection is disconnected
|
||||
* @module disconnect
|
||||
*/
|
||||
|
||||
import {
|
||||
socketInChannel,
|
||||
} from '../utility/_Channels.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ server, socket, payload }) {
|
||||
if (payload.cmdKey !== server.cmdKey) {
|
||||
// internal command attempt by client, increase rate limit chance and ignore
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// send leave notice to client peers
|
||||
// @todo Multichannel update
|
||||
if (socket.channel) {
|
||||
const isDuplicate = socketInChannel(server, socket.channel, socket);
|
||||
|
||||
if (isDuplicate === false) {
|
||||
server.broadcast({
|
||||
cmd: 'onlineRemove',
|
||||
nick: socket.nick,
|
||||
}, { channel: socket.channel });
|
||||
}
|
||||
}
|
||||
|
||||
// commit close just in case
|
||||
socket.terminate();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "cmdKey"
|
||||
* @public
|
||||
* @typedef {Array} disconnect/requiredData
|
||||
*/
|
||||
export const requiredData = ['cmdKey'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} disconnect/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'disconnect',
|
||||
category: 'internal',
|
||||
description: 'Internally used to relay disconnect events to clients',
|
||||
usage: 'Internal Use Only',
|
||||
};
|
51
commands/internal/socketreply.js
Normal file
51
commands/internal/socketreply.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Bridge warning events to a user
|
||||
* @version 1.0.0
|
||||
* @description If a warning occurs within the server, this module will relay the warning to the
|
||||
* client
|
||||
* @module socketreply
|
||||
*/
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ server, socket, payload }) {
|
||||
if (payload.cmdKey !== server.cmdKey) {
|
||||
// internal command attempt by client, increase rate limit chance and ignore
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// send warning to target socket
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: payload.text,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "cmdKey", "text"
|
||||
* @public
|
||||
* @typedef {Array} socketreply/requiredData
|
||||
*/
|
||||
export const requiredData = ['cmdKey', 'text'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} socketreply/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'socketreply',
|
||||
category: 'internal',
|
||||
description: 'Internally used to relay warnings to clients',
|
||||
usage: 'Internal Use Only',
|
||||
};
|
115
commands/mod/ban.js
Normal file
115
commands/mod/ban.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Ban a user
|
||||
* @version 1.0.0
|
||||
* @description Bans target user by name
|
||||
* @module ban
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
getUserDetails,
|
||||
levels,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof payload.nick !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
} else if (typeof payload.userid !== 'number') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// find target user
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
const targetNick = targetUser.nick;
|
||||
|
||||
// i guess banning mods or admins isn't the best idea?
|
||||
if (targetUser.level >= socket.level) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Cannot ban other users of the same level, how rude',
|
||||
id: Errors.Global.PERMISSION,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// commit arrest record
|
||||
server.police.arrest(targetUser.address, targetUser.hash);
|
||||
|
||||
console.log(`${socket.nick} [${socket.trip}] banned ${targetNick} in ${socket.channel}`);
|
||||
|
||||
// notify normal users
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Banned ${targetNick}`,
|
||||
user: getUserDetails(targetUser),
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, { channel: socket.channel, level: (level) => level < levels.moderator });
|
||||
|
||||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick}#${socket.trip} banned ${targetNick} in ${payload.channel}, userhash: ${targetUser.hash}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
inChannel: payload.channel,
|
||||
user: getUserDetails(targetUser),
|
||||
banner: getUserDetails(socket),
|
||||
}, { level: isModerator });
|
||||
|
||||
// force connection closed
|
||||
targetUser.terminate();
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('users-banned');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} ban/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'ban',
|
||||
category: 'moderators',
|
||||
description: 'Bans target user by name',
|
||||
usage: `
|
||||
API: { cmd: 'ban', nick: '<target nickname>' }`,
|
||||
};
|
86
commands/mod/disablecaptcha.js
Normal file
86
commands/mod/disablecaptcha.js
Normal file
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Disables the captcha
|
||||
* @version 1.0.0
|
||||
* @description Disables the captcha on the channel specified in the channel property,
|
||||
* default is current channel
|
||||
* @module disablecaptcha
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready
|
||||
* @param {Object} core - Reference to core enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function init(core) {
|
||||
if (typeof core.captchas === 'undefined') {
|
||||
core.captchas = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
let targetChannel;
|
||||
|
||||
if (typeof payload.channel !== 'string') {
|
||||
if (typeof socket.channel !== 'string') { // @todo Multichannel
|
||||
return false; // silently fail
|
||||
}
|
||||
|
||||
targetChannel = socket.channel;
|
||||
} else {
|
||||
targetChannel = payload.channel;
|
||||
}
|
||||
|
||||
if (!core.captchas[targetChannel]) {
|
||||
return server.reply({
|
||||
cmd: 'info',
|
||||
text: 'Captcha is not enabled.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
core.captchas[targetChannel] = false;
|
||||
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Captcha disabled on: ${targetChannel}`,
|
||||
channel: false, // @todo Multichannel, false for global info
|
||||
}, { channel: targetChannel, level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} disablecaptcha/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'disablecaptcha',
|
||||
category: 'moderators',
|
||||
description: 'Disables the captcha on the channel specified in the channel property, default is current channel',
|
||||
usage: `
|
||||
API: { cmd: 'disablecaptcha', channel: '<optional channel, defaults to your current channel' }`,
|
||||
};
|
374
commands/mod/dumb.js
Normal file
374
commands/mod/dumb.js
Normal file
|
@ -0,0 +1,374 @@
|
|||
/* eslint no-param-reassign: 0 */
|
||||
/* eslint no-multi-assign: 0 */
|
||||
|
||||
/**
|
||||
* @author OpSimple ( https://github.com/OpSimple )
|
||||
* @summary Muzzle a user
|
||||
* @version 1.0.0
|
||||
* @description Globally shadow mute a connection. Optional allies array will see muted messages.
|
||||
* @module dumb
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
legacyInviteReply,
|
||||
legacyWhisperReply,
|
||||
} from '../utility/_LegacyFunctions.js';
|
||||
|
||||
/**
|
||||
* Returns the channel that should be invited to.
|
||||
* @param {any} channel
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
export function getChannel(channel = undefined) {
|
||||
if (typeof channel === 'string') {
|
||||
return channel;
|
||||
}
|
||||
return Math.random().toString(36).substr(2, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and trim string provided by remote client
|
||||
* @param {string} text - Subject string
|
||||
* @private
|
||||
* @todo Move into utility module
|
||||
* @return {string|boolean}
|
||||
*/
|
||||
const parseText = (text) => {
|
||||
// verifies user input is text
|
||||
if (typeof text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let sanitizedText = 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready
|
||||
* @param {Object} core - Reference to core enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function init(core) {
|
||||
if (typeof core.muzzledHashes === 'undefined') {
|
||||
core.muzzledHashes = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
payload.channel = socket.channel;
|
||||
} else if (typeof payload.userid !== 'number') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// find target user
|
||||
const targetUser = findUser(server, payload);
|
||||
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// likely dont need this, muting mods and admins is fine
|
||||
if (targetUser.level >= socket.level) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'This trick wont work on users of the same level',
|
||||
id: Errors.Global.PERMISSION,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// store hash in mute list
|
||||
const record = core.muzzledHashes[targetUser.hash] = {
|
||||
dumb: true,
|
||||
};
|
||||
|
||||
// store allies if needed
|
||||
if (payload.allies && Array.isArray(payload.allies)) {
|
||||
record.allies = payload.allies;
|
||||
}
|
||||
|
||||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick}#${socket.trip} muzzled ${targetUser.nick} in ${payload.channel}, userhash: ${targetUser.hash}`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.chatCheck.bind(this), 10);
|
||||
server.registerHook('in', 'invite', this.inviteCheck.bind(this), 10);
|
||||
server.registerHook('in', 'whisper', this.whisperCheck.bind(this), 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hook incoming chat commands, shadow-prevent chat if they are muzzled
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function chatCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (core.muzzledHashes[socket.hash]) {
|
||||
// build fake chat payload
|
||||
const outgoingPayload = {
|
||||
cmd: 'chat',
|
||||
nick: socket.nick, /* @legacy */
|
||||
uType: socket.uType, /* @legacy */
|
||||
userid: socket.userid,
|
||||
channel: socket.channel,
|
||||
text: payload.text,
|
||||
level: socket.level,
|
||||
};
|
||||
|
||||
if (socket.trip) {
|
||||
outgoingPayload.trip = socket.trip;
|
||||
}
|
||||
|
||||
if (socket.color) {
|
||||
outgoingPayload.color = socket.color;
|
||||
}
|
||||
|
||||
// broadcast to any duplicate connections in channel
|
||||
server.broadcast(outgoingPayload, { channel: socket.channel, hash: socket.hash });
|
||||
|
||||
// broadcast to allies, if any
|
||||
if (core.muzzledHashes[socket.hash].allies) {
|
||||
server.broadcast(
|
||||
outgoingPayload,
|
||||
{
|
||||
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, 9);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* shadow-prevent all invites from muzzled users
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function inviteCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (core.muzzledHashes[socket.hash]) {
|
||||
// check for spam
|
||||
if (server.police.frisk(socket, 2)) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You are sending invites too fast. Wait a moment before trying again.',
|
||||
id: Errors.Invite.RATELIMIT,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// verify user input
|
||||
// if this is a legacy client add missing params to payload
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof socket.channel === 'undefined' || typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
} else if (typeof payload.userid !== 'number' || typeof payload.channel !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// @todo Verify this socket is part of payload.channel - multichannel patch
|
||||
// find target user
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// generate common channel
|
||||
const channel = getChannel(payload.to);
|
||||
|
||||
// build invite
|
||||
const outgoingPayload = {
|
||||
cmd: 'invite',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
from: socket.userid,
|
||||
to: targetUser.userid,
|
||||
inviteChannel: channel,
|
||||
};
|
||||
|
||||
// send invite notice to this client
|
||||
if (socket.hcProtocol === 1) {
|
||||
server.reply(legacyInviteReply(outgoingPayload, targetUser.nick), socket);
|
||||
} else {
|
||||
server.reply(outgoingPayload, socket);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* shadow-prevent all whispers from muzzled users
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function whisperCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (core.muzzledHashes[socket.hash]) {
|
||||
// if this is a legacy client add missing params to payload
|
||||
if (socket.hcProtocol === 1) {
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
// verify user input
|
||||
const text = parseText(payload.text);
|
||||
|
||||
if (!text) {
|
||||
// lets not send objects or empty text, yea?
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
// check for spam
|
||||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket, score)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
const outgoingPayload = {
|
||||
cmd: 'whisper',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
from: socket.userid,
|
||||
to: targetUser.userid,
|
||||
text,
|
||||
};
|
||||
|
||||
// send invite notice to this client
|
||||
if (socket.hcProtocol === 1) {
|
||||
server.reply(legacyWhisperReply(outgoingPayload, targetUser.nick), socket);
|
||||
} else {
|
||||
server.reply(outgoingPayload, socket);
|
||||
}
|
||||
|
||||
targetUser.whisperReply = socket.nick;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} dumb/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {Array} aliases - An array of alternative cmd names
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'dumb',
|
||||
category: 'moderators',
|
||||
description: 'Globally shadow mute a connection. Optional allies array will see muted messages.',
|
||||
aliases: ['muzzle', 'mute'],
|
||||
usage: `
|
||||
API: { cmd: 'dumb', nick: '<target nick>', allies: ['<optional nick array>', ...] }`,
|
||||
};
|
279
commands/mod/enablecaptcha.js
Normal file
279
commands/mod/enablecaptcha.js
Normal file
|
@ -0,0 +1,279 @@
|
|||
/* eslint no-param-reassign: 0 */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Enables the captcha
|
||||
* @version 1.0.0
|
||||
* @description Enables the captcha on the channel specified in the channel property,
|
||||
* default is current channel
|
||||
* @module enablecaptcha
|
||||
*/
|
||||
|
||||
import captcha from 'ascii-captcha';
|
||||
|
||||
import {
|
||||
isTrustedUser,
|
||||
isModerator,
|
||||
verifyNickname,
|
||||
getUserPerms,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
canJoinChannel,
|
||||
} from '../utility/_Channels.js';
|
||||
import {
|
||||
upgradeLegacyJoin,
|
||||
legacyLevelToLabel,
|
||||
} from '../utility/_LegacyFunctions.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready
|
||||
* @param {Object} core - Reference to core enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function init(core) {
|
||||
if (typeof core.captchas === 'undefined') {
|
||||
core.captchas = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
let targetChannel;
|
||||
|
||||
if (typeof payload.channel !== 'string') {
|
||||
if (typeof socket.channel !== 'string') { // @todo Multichannel
|
||||
return false; // silently fail
|
||||
}
|
||||
|
||||
targetChannel = socket.channel;
|
||||
} else {
|
||||
targetChannel = payload.channel;
|
||||
}
|
||||
|
||||
if (core.captchas[targetChannel]) {
|
||||
return server.reply({
|
||||
cmd: 'info',
|
||||
text: 'Captcha is already enabled.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
core.captchas[targetChannel] = true;
|
||||
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Captcha enabled on: ${targetChannel}`,
|
||||
channel: socket.channel, // @todo Multichannel, false for global info
|
||||
}, { channel: socket.channel, level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.chatCheck.bind(this), 5);
|
||||
server.registerHook('in', 'join', this.joinCheck.bind(this), 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hook incoming chat commands, check if they are answering a captcha
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function chatCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// always verifiy user input
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof socket.captcha !== 'undefined') {
|
||||
if (socket.captcha.awaiting === true) {
|
||||
if (payload.text === socket.captcha.solution) {
|
||||
if (typeof socket.captcha.whitelist === 'undefined') {
|
||||
socket.captcha.whitelist = [];
|
||||
}
|
||||
|
||||
socket.captcha.whitelist.push(socket.captcha.origChannel);
|
||||
socket.captcha.awaiting = false;
|
||||
|
||||
if (socket.hcProtocol === 1) {
|
||||
core.commands.handleCommand(server, socket, {
|
||||
cmd: 'join',
|
||||
nick: `${socket.captcha.origNick}#${socket.captcha.origPass}`,
|
||||
channel: socket.captcha.origChannel,
|
||||
});
|
||||
} else {
|
||||
core.commands.handleCommand(server, socket, {
|
||||
cmd: 'join',
|
||||
nick: socket.captcha.origNick,
|
||||
pass: socket.captcha.origPass,
|
||||
channel: socket.captcha.origChannel,
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
server.police.frisk(socket, 7);
|
||||
socket.terminate();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming join command is invoked;
|
||||
* hook incoming join commands, check if they are joining a captcha protected channel
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function joinCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// check if channel has captcha enabled
|
||||
if (core.captchas[payload.channel] !== true) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
// `join` is the legacy entry point, check if it needs to be upgraded
|
||||
const origPayload = { ...payload };
|
||||
if (typeof socket.hcProtocol === 'undefined') {
|
||||
payload = upgradeLegacyJoin(server, socket, payload);
|
||||
}
|
||||
|
||||
// store payload values
|
||||
const { channel, nick, pass } = payload;
|
||||
|
||||
// check if a client is able to join target channel
|
||||
const mayJoin = canJoinChannel(channel, socket);
|
||||
if (mayJoin !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You may not join that channel.',
|
||||
id: mayJoin,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// calling socket already in a channel
|
||||
// @todo multichannel update, will remove
|
||||
if (typeof socket.channel !== 'undefined') {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove this
|
||||
text: 'Joining more than one channel is not currently supported',
|
||||
id: Errors.Join.ALREADY_JOINED,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
// end todo
|
||||
|
||||
// validates the user input for `nick`
|
||||
if (verifyNickname(nick, socket) !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Nickname must consist of up to 24 letters, numbers, and underscores',
|
||||
id: Errors.Join.INVALID_NICK,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// get trip and level
|
||||
const { trip, level } = getUserPerms(pass, core.saltKey, core.appConfig.data, channel);
|
||||
|
||||
// store the user values
|
||||
const userInfo = {
|
||||
nick,
|
||||
trip,
|
||||
uType: legacyLevelToLabel(level),
|
||||
hash: socket.hash,
|
||||
level,
|
||||
userid: socket.userid,
|
||||
isBot: socket.isBot,
|
||||
color: socket.color,
|
||||
channel,
|
||||
};
|
||||
|
||||
if (userInfo.uType === 'user') {
|
||||
if (userInfo.trip == null || isTrustedUser(level) === false) {
|
||||
if (typeof socket.captcha === 'undefined') {
|
||||
socket.captcha = {
|
||||
awaiting: true,
|
||||
origChannel: payload.channel,
|
||||
origNick: payload.nick,
|
||||
origPass: pass,
|
||||
solution: captcha.generateRandomText(6),
|
||||
};
|
||||
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Enter the following to join (case-sensitive):',
|
||||
channel: payload.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
server.reply({
|
||||
cmd: 'captcha',
|
||||
text: captcha.word2Transformedstr(socket.captcha.solution),
|
||||
channel: payload.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
socket.terminate();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return origPayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} enablecaptcha/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'enablecaptcha',
|
||||
category: 'moderators',
|
||||
description: 'Enables a captcha in the current channel you are in',
|
||||
usage: `
|
||||
API: { cmd: 'enablecaptcha', channel: '<optional channel, defaults to your current channel>' }`,
|
||||
};
|
199
commands/mod/forcecolor.js
Normal file
199
commands/mod/forcecolor.js
Normal file
|
@ -0,0 +1,199 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Color a user
|
||||
* @version 1.0.0
|
||||
* @description Forces a user nick to become a certain color
|
||||
* @module forcecolor
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels.js';
|
||||
|
||||
/**
|
||||
* Validate a string as a valid hex color string
|
||||
* @param {string} color - Color string to validate
|
||||
* @private
|
||||
* @todo Move into utility module
|
||||
* @return {boolean}
|
||||
*/
|
||||
const verifyColor = (color) => /(^[0-9A-F]{6}$)|(^[0-9A-F]{3}$)/i.test(color);
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
const { channel } = socket;
|
||||
if (typeof payload.channel === 'undefined') {
|
||||
payload.channel = channel;
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof payload.color !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// make sure requested nickname meets standards
|
||||
const newColor = payload.color.trim().toUpperCase().replace(/#/g, '');
|
||||
if (newColor !== 'RESET' && !verifyColor(newColor)) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Invalid color! Color must be in hex value',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// find target user
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// TODO: Change this uType to use level / uac
|
||||
// i guess coloring mods or admins isn't the best idea?
|
||||
if (targetUser.uType !== 'user') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (newColor === 'RESET') {
|
||||
targetUser.color = false;
|
||||
} else {
|
||||
targetUser.color = newColor;
|
||||
}
|
||||
|
||||
// build update notice with new color
|
||||
const updateNotice = {
|
||||
...getUserDetails(targetUser),
|
||||
...{
|
||||
cmd: 'updateUser',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
},
|
||||
};
|
||||
|
||||
// notify channel that the user has changed their name
|
||||
// @todo this should be sent to every channel the user is in (multichannel)
|
||||
server.broadcast(updateNotice, { channel: socket.channel });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.colorCheck.bind(this), 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hooks chat commands checking for /forcecolor
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function colorCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/forcecolor ')) {
|
||||
const input = payload.text.split(' ');
|
||||
|
||||
// If there is no nickname target parameter
|
||||
if (input[1] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Refer to `/help forcecolor` for instructions on how to use this command.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (input[2] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Refer to `/help forcecolor` for instructions on how to use this command.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = input[1].replace(/@/g, '');
|
||||
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'forcecolor',
|
||||
nick: target,
|
||||
color: input[2],
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "nick", "color"
|
||||
* @public
|
||||
* @typedef {Array} forcecolor/requiredData
|
||||
*/
|
||||
export const requiredData = ['nick', 'color'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} forcecolor/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'forcecolor',
|
||||
category: 'moderators',
|
||||
description: 'Forces a user nick to become a certain color',
|
||||
usage: `
|
||||
API: { cmd: 'forcecolor', nick: '<target nick>', color: '<color as hex>' }
|
||||
Text: /forcecolor <target nick> <color as hex>`,
|
||||
};
|
152
commands/mod/kick.js
Normal file
152
commands/mod/kick.js
Normal file
|
@ -0,0 +1,152 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Give da boot
|
||||
* @version 1.0.0
|
||||
* @description Silently forces target client(s) into another channel
|
||||
* @module kick
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
levels,
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
findUsers,
|
||||
} from '../utility/_Channels.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof payload.nick !== 'string') {
|
||||
if (typeof payload.nick !== 'object' && !Array.isArray(payload.nick)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
} else if (typeof payload.userid !== 'number') {
|
||||
// @todo create multi-ban ui
|
||||
if (typeof payload.userid !== 'object' && !Array.isArray(payload.userid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// find target user(s)
|
||||
const badClients = findUsers(server, payload);
|
||||
if (badClients.length === 0) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user(s) in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// check if found targets are kickable, add them to the list if they are
|
||||
const kicked = [];
|
||||
for (let i = 0, j = badClients.length; i < j; i += 1) {
|
||||
if (badClients[i].level >= socket.level) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Cannot kick other users with the same level, how rude',
|
||||
id: Errors.Global.PERMISSION,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
} else {
|
||||
kicked.push(badClients[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (kicked.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let destChannel;
|
||||
if (typeof payload.to === 'string' && !!payload.to.trim()) {
|
||||
destChannel = payload.to;
|
||||
} else {
|
||||
destChannel = Math.random().toString(36).substr(2, 8);
|
||||
}
|
||||
|
||||
// Announce the kicked clients arrival in destChannel and that they were kicked
|
||||
// Before they arrive, so they don't see they got moved
|
||||
for (let i = 0; i < kicked.length; i += 1) {
|
||||
server.broadcast({
|
||||
...getUserDetails(kicked[i]),
|
||||
...{
|
||||
cmd: 'onlineAdd',
|
||||
channel: destChannel, // @todo Multichannel
|
||||
},
|
||||
}, { channel: destChannel });
|
||||
}
|
||||
|
||||
// Move all kicked clients to the new channel
|
||||
for (let i = 0; i < kicked.length; i += 1) {
|
||||
// @todo multi-channel update
|
||||
kicked[i].channel = destChannel;
|
||||
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${kicked[i].nick} was banished to ?${destChannel}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, { channel: socket.channel, level: isModerator });
|
||||
|
||||
console.log(`${socket.nick} [${socket.trip}] kicked ${kicked[i].nick} in ${socket.channel} to ${destChannel} `);
|
||||
}
|
||||
|
||||
// broadcast client leave event
|
||||
for (let i = 0, j = kicked.length; i < j; i += 1) {
|
||||
server.broadcast({
|
||||
cmd: 'onlineRemove',
|
||||
userid: kicked[i].userid,
|
||||
nick: kicked[i].nick,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, { channel: socket.channel });
|
||||
}
|
||||
|
||||
// publicly broadcast kick event
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Kicked ${kicked.map((k) => k.nick).join(', ')}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, { channel: socket.channel, level: (level) => level < levels.moderator });
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('users-kicked', kicked.length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} kick/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'kick',
|
||||
category: 'moderators',
|
||||
description: 'Silently forces target client(s) into another channel. `nick` may be string or array of strings',
|
||||
usage: `
|
||||
API: { cmd: 'kick', nick: '<target nick>', to: '<optional target channel>' }`,
|
||||
};
|
331
commands/mod/lockroom.js
Normal file
331
commands/mod/lockroom.js
Normal file
|
@ -0,0 +1,331 @@
|
|||
/* eslint no-param-reassign: 0 */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Locks the channel
|
||||
* @version 1.0.0
|
||||
* @description Locks a channel preventing default levels from joining
|
||||
* @module lockroom
|
||||
*/
|
||||
|
||||
import {
|
||||
isTrustedUser,
|
||||
isModerator,
|
||||
verifyNickname,
|
||||
getUserPerms,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
upgradeLegacyJoin,
|
||||
legacyLevelToLabel,
|
||||
} from '../utility/_LegacyFunctions.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
canJoinChannel,
|
||||
} from '../utility/_Channels.js';
|
||||
|
||||
const danteQuotes = [
|
||||
'Do not be afraid; our fate cannot be taken from us; it is a gift.',
|
||||
'In the middle of the journey of our life I found myself within a dark woods where the straight way was lost.',
|
||||
'There is no greater sorrow then to recall our times of joy in wretchedness.',
|
||||
'They yearn for what they fear for.',
|
||||
'Through me you go into a city of weeping; through me you go into eternal pain; through me you go amongst the lost people',
|
||||
'From there we came outside and saw the stars',
|
||||
'But the stars that marked our starting fall away. We must go deeper into greater pain, for it is not permitted that we stay.',
|
||||
'Hope not ever to see Heaven. I have come to lead you to the other shore; into eternal darkness; into fire and into ice.',
|
||||
'As little flowers, which the chill of night has bent and huddled, when the white sun strikes, grow straight and open fully on their stems, so did I, too, with my exhausted force.',
|
||||
'At grief so deep the tongue must wag in vain; the language of our sense and memory lacks the vocabulary of such pain.',
|
||||
'Thence we came forth to rebehold the stars.',
|
||||
'He is, most of all, l\'amor che move il sole e l\'altre stelle.',
|
||||
'The poets leave hell and again behold the stars.',
|
||||
'One ought to be afraid of nothing other then things possessed of power to do us harm, but things innoucuous need not be feared.',
|
||||
'As phantoms frighten beasts when shadows fall.',
|
||||
'We were men once, though we\'ve become trees',
|
||||
'Here pity only lives when it is dead',
|
||||
'Lasciate ogne speranza, voi ch\'intrate.',
|
||||
'There is no greater sorrow than thinking back upon a happy time in misery',
|
||||
'My thoughts were full of other things When I wandered off the path.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready
|
||||
* @param {Object} core - Reference to core enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function init(core) {
|
||||
if (typeof core.locked === 'undefined') {
|
||||
core.locked = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
let targetChannel;
|
||||
|
||||
if (typeof payload.channel !== 'string') {
|
||||
if (typeof socket.channel !== 'string') { // @todo Multichannel
|
||||
return false; // silently fail
|
||||
}
|
||||
|
||||
targetChannel = socket.channel;
|
||||
} else {
|
||||
targetChannel = payload.channel;
|
||||
}
|
||||
|
||||
if (core.locked[targetChannel]) {
|
||||
return server.reply({
|
||||
cmd: 'info',
|
||||
text: 'Channel is already locked.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// apply lock flag to channel list
|
||||
core.locked[targetChannel] = true;
|
||||
|
||||
// inform mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Channel: ?${targetChannel} lock enabled by [${socket.trip}]${socket.nick}`,
|
||||
channel: false, // @todo Multichannel, false for global info
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'changenick', this.changeNickCheck.bind(this), 1);
|
||||
server.registerHook('in', 'whisper', this.whisperCheck.bind(this), 1);
|
||||
server.registerHook('in', 'chat', this.chatCheck.bind(this), 1);
|
||||
server.registerHook('in', 'invite', this.inviteCheck.bind(this), 1);
|
||||
server.registerHook('in', 'join', this.joinCheck.bind(this), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming changenick command is invoked;
|
||||
* hook incoming changenick commands, reject them if the channel is 'purgatory'
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function changeNickCheck({
|
||||
socket, payload,
|
||||
}) {
|
||||
if (socket.channel === 'purgatory') { // @todo Multichannel update
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming whisper command is invoked;
|
||||
* hook incoming whisper commands, reject them if the channel is 'purgatory'
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function whisperCheck({
|
||||
socket, payload,
|
||||
}) {
|
||||
if (socket.channel === 'purgatory') { // @todo Multichannel update
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hook incoming chat commands, reject them if the channel is 'purgatory'
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function chatCheck({
|
||||
socket, payload,
|
||||
}) {
|
||||
if (socket.channel === 'purgatory') {
|
||||
if (isModerator(socket.level)) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming invite command is invoked;
|
||||
* hook incoming invite commands, reject them if the channel is 'purgatory'
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function inviteCheck({
|
||||
socket, payload,
|
||||
}) {
|
||||
if (socket.channel === 'purgatory') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming join command is invoked;
|
||||
* hook incoming join commands, shunt them to purgatory if needed
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function joinCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// check if target channel is locked
|
||||
if (typeof core.locked[payload.channel] === 'undefined' || core.locked[payload.channel] !== true) {
|
||||
if (payload.channel !== 'purgatory') {
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
// `join` is the legacy entry point, check if it needs to be upgraded
|
||||
if (typeof socket.hcProtocol === 'undefined') {
|
||||
payload = upgradeLegacyJoin(server, socket, payload);
|
||||
}
|
||||
|
||||
// store payload values
|
||||
const { channel, nick, pass } = payload;
|
||||
|
||||
// check if a client is able to join target channel
|
||||
const mayJoin = canJoinChannel(channel, socket);
|
||||
if (mayJoin !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You may not join that channel.',
|
||||
id: mayJoin,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// calling socket already in a channel
|
||||
// @todo multichannel update, will remove
|
||||
if (typeof socket.channel !== 'undefined') {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove this
|
||||
text: 'Joining more than one channel is not currently supported',
|
||||
id: Errors.Join.ALREADY_JOINED,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
// end todo
|
||||
|
||||
// validates the user input for `nick`
|
||||
if (verifyNickname(nick, socket) !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Nickname must consist of up to 24 letters, numbers, and underscores',
|
||||
id: Errors.Join.INVALID_NICK,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// get trip and level
|
||||
const { trip, level } = getUserPerms(pass, core.saltKey, core.appConfig.data, channel);
|
||||
|
||||
// store the user values
|
||||
const userInfo = {
|
||||
nick,
|
||||
trip,
|
||||
uType: legacyLevelToLabel(level),
|
||||
hash: socket.hash,
|
||||
level,
|
||||
userid: socket.userid,
|
||||
isBot: socket.isBot,
|
||||
color: socket.color,
|
||||
channel,
|
||||
};
|
||||
|
||||
// check if trip is allowed
|
||||
if (userInfo.uType === 'user') {
|
||||
if (userInfo.trip == null || isTrustedUser(level) === false) {
|
||||
const origNick = userInfo.nick;
|
||||
const origChannel = payload.channel;
|
||||
|
||||
// not allowed, shunt to purgatory
|
||||
payload.channel = 'purgatory';
|
||||
|
||||
// lost souls have no names
|
||||
if (origChannel === 'purgatory') {
|
||||
// someone is pulling a Dante
|
||||
payload.nick = `Dante_${Math.random().toString(36).substr(2, 8)}`;
|
||||
} else {
|
||||
payload.nick = `${Math.random().toString(36).substr(2, 8)}${Math.random().toString(36).substr(2, 8)}`;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: danteQuotes[Math.floor(Math.random() * danteQuotes.length)],
|
||||
channel: 'purgatory', // @todo Multichannel
|
||||
}, socket);
|
||||
}, 100);
|
||||
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${payload.nick} is: ${origNick}\ntrip: ${userInfo.trip || 'none'}\ntried to join: ?${origChannel}\nhash: ${userInfo.hash}`,
|
||||
channel: 'purgatory', // @todo Multichannel, false for global info
|
||||
}, { channel: 'purgatory', level: isModerator });
|
||||
}
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} kick/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'lockroom',
|
||||
category: 'moderators',
|
||||
description: 'Locks a channel preventing default levels from joining',
|
||||
usage: `
|
||||
API: { cmd: 'lockroom', channel: '<optional channel, defaults to your current channel>' }`,
|
||||
};
|
107
commands/mod/speak.js
Normal file
107
commands/mod/speak.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
/* eslint no-param-reassign: 0 */
|
||||
|
||||
/**
|
||||
* @author OpSimple ( https://github.com/OpSimple )
|
||||
* @summary Unmuzzle a user
|
||||
* @version 1.0.0
|
||||
* @description Pardon a dumb user to be able to speak again
|
||||
* @module speak
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready
|
||||
* @param {Object} core - Reference to core enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function init(core) {
|
||||
if (typeof core.muzzledHashes === 'undefined') {
|
||||
core.muzzledHashes = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof payload.ip !== 'string' && typeof payload.hash !== 'string') {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: "hash:'targethash' or ip:'1.2.3.4' is required",
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
if (typeof payload.ip === 'string') {
|
||||
if (payload.ip === '*') {
|
||||
core.muzzledHashes = {};
|
||||
|
||||
return server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick} unmuzzled all users`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
}
|
||||
} else if (payload.hash === '*') {
|
||||
core.muzzledHashes = {};
|
||||
|
||||
return server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick} unmuzzled all users`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
}
|
||||
|
||||
// find target & remove mute status
|
||||
let target;
|
||||
if (typeof payload.ip === 'string') {
|
||||
target = server.getSocketHash(payload.ip);
|
||||
} else {
|
||||
target = payload.hash;
|
||||
}
|
||||
|
||||
delete core.muzzledHashes[target];
|
||||
|
||||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick}#${socket.trip} unmuzzled : ${target}`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} speak/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {Array} aliases - An array of alternative cmd names
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'speak',
|
||||
category: 'moderators',
|
||||
description: 'Pardon a dumb user to be able to speak again',
|
||||
aliases: ['unmuzzle', 'unmute'],
|
||||
usage: `
|
||||
API: { cmd: 'speak', ip/hash: '<target ip or hash>' }`,
|
||||
};
|
91
commands/mod/unban.js
Normal file
91
commands/mod/unban.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Unban a user
|
||||
* @version 1.0.0
|
||||
* @description Un-bans target user by ip or hash
|
||||
* @module unban
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof payload.ip !== 'string' && typeof payload.hash !== 'string') {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: "hash:'targethash' or ip:'1.2.3.4' is required",
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// find target
|
||||
let mode;
|
||||
let target;
|
||||
if (typeof payload.ip === 'string') {
|
||||
mode = 'ip';
|
||||
target = payload.ip;
|
||||
} else {
|
||||
mode = 'hash';
|
||||
target = payload.hash;
|
||||
}
|
||||
|
||||
// remove arrest record
|
||||
server.police.pardon(target);
|
||||
|
||||
// mask ip if used
|
||||
if (mode === 'ip') {
|
||||
target = server.getSocketHash(target);
|
||||
}
|
||||
console.log(`${socket.nick} [${socket.trip}] unbanned ${target} in ${socket.channel}`);
|
||||
|
||||
// reply with success
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `Unbanned ${target}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick}#${socket.trip} unbanned: ${target}`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
|
||||
// stats are fun
|
||||
core.stats.decrement('users-banned');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} unban/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'unban',
|
||||
category: 'moderators',
|
||||
description: 'Un-bans target user by ip or hash',
|
||||
usage: `
|
||||
API: { cmd: 'unban', ip/hash: '<target ip or hash>' }`,
|
||||
};
|
64
commands/mod/unbanall.js
Normal file
64
commands/mod/unbanall.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Released them from the void
|
||||
* @version 1.0.0
|
||||
* @description Clears all banned ip addresses
|
||||
* @module unbanall
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ core, server, socket }) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
// remove arrest records
|
||||
server.police.clear();
|
||||
|
||||
core.stats.set('users-banned', 0);
|
||||
|
||||
console.log(`${socket.nick} [${socket.trip}] unbanned all`);
|
||||
|
||||
// reply with success
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: 'Unbanned all ip addresses',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick}#${socket.trip} unbanned all ip addresses`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} unbanall/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'unbanall',
|
||||
category: 'moderators',
|
||||
description: 'Clears all banned ip addresses',
|
||||
usage: `
|
||||
API: { cmd: 'unbanall' }`,
|
||||
};
|
89
commands/mod/unlockroom.js
Normal file
89
commands/mod/unlockroom.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
/* eslint no-console: 0 */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Unlock target channel
|
||||
* @version 1.0.0
|
||||
* @description Unlocks a channel allowing anyone to join
|
||||
* @module unlockroom
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready
|
||||
* @param {Object} core - Reference to core enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function init(core) {
|
||||
if (typeof core.locked === 'undefined') {
|
||||
core.locked = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
let targetChannel;
|
||||
|
||||
if (typeof payload.channel !== 'string') {
|
||||
if (typeof socket.channel !== 'string') { // @todo Multichannel
|
||||
return false; // silently fail
|
||||
}
|
||||
|
||||
targetChannel = socket.channel;
|
||||
} else {
|
||||
targetChannel = payload.channel;
|
||||
}
|
||||
|
||||
if (!core.locked[targetChannel]) {
|
||||
return server.reply({
|
||||
cmd: 'info',
|
||||
text: 'Channel is not locked.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
core.locked[targetChannel] = false;
|
||||
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Channel: ?${targetChannel} unlocked by [${socket.trip}]${socket.nick}`,
|
||||
channel: targetChannel, // @todo Multichannel, false for global info
|
||||
}, { channel: targetChannel, level: isModerator });
|
||||
|
||||
console.log(`Channel: ?${targetChannel} unlocked by [${socket.trip}]${socket.nick} in ${socket.channel}`);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} unlockroom/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'unlockroom',
|
||||
category: 'moderators',
|
||||
description: 'Unlock the current channel you are in or target channel as specified',
|
||||
usage: `
|
||||
API: { cmd: 'unlockroom', channel: '<optional target channel>' }`,
|
||||
};
|
108
commands/utility/_Channels.js
Normal file
108
commands/utility/_Channels.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Channel helper
|
||||
* @version 1.0.0
|
||||
* @description Functions to assist with channel manipulation
|
||||
* @module Channels
|
||||
*/
|
||||
|
||||
import {
|
||||
Errors,
|
||||
} from './_Constants.js';
|
||||
|
||||
/**
|
||||
* Checks if a client can join `channel`, returns numeric error code or true if
|
||||
* able to join
|
||||
* @public
|
||||
* @param {string} channel Target channel
|
||||
* @param {object} socket Target client to evaluate
|
||||
* @return {boolean||error id}
|
||||
*/
|
||||
export function canJoinChannel(channel, socket) {
|
||||
if (typeof channel !== 'string') return Errors.Channel.INVALID_NAME;
|
||||
if (channel === '') return Errors.Channel.INVALID_NAME;
|
||||
if (channel.length > 120) return Errors.Channel.INVALID_LENGTH;
|
||||
|
||||
if (typeof socket.banned !== 'undefined' && socket.banned) return Errors.Channel.DEY_BANNED;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing info about the specified channel,
|
||||
* including if it is owned, mods, permissions
|
||||
* @public
|
||||
* @param {string} config Server config object
|
||||
* @param {string} channel Target channel
|
||||
* @return {object}
|
||||
*/
|
||||
export function getChannelSettings(config, channel) {
|
||||
if (typeof config.permissions !== 'undefined') {
|
||||
if (typeof config.permissions[channel] !== 'undefined') {
|
||||
return config.permissions[channel];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
owned: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing info about the specified channel,
|
||||
* including if it is owned, mods, permissions
|
||||
* @public
|
||||
* @param {MainServer} server Main server reference
|
||||
* @param {object} payload Object containing `userid` or `nick`
|
||||
* @param {number} limit Optional return limit
|
||||
* @return {array}
|
||||
*/
|
||||
export function findUsers(server, payload, limit = 0) {
|
||||
let targetClients;
|
||||
|
||||
if (typeof payload.userid !== 'undefined') {
|
||||
targetClients = server.findSockets({
|
||||
channel: payload.channel,
|
||||
userid: payload.userid,
|
||||
});
|
||||
} else if (typeof payload.nick !== 'undefined') {
|
||||
targetClients = server.findSockets({
|
||||
channel: payload.channel,
|
||||
nick: payload.nick,
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (limit !== 0 && targetClients.length > limit) {
|
||||
return targetClients.splice(0, limit);
|
||||
}
|
||||
|
||||
return targetClients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload for `findUsers` when only 1 user is expected
|
||||
* @public
|
||||
* @param {MainServer} server Main server reference
|
||||
* @param {object} payload Object containing `userid` or `nick`
|
||||
* @param {number} limit Optional return limit
|
||||
* @return {boolean||object}
|
||||
*/
|
||||
export function findUser(server, payload) {
|
||||
return findUsers(server, payload, 1)[0] || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the target socket's userid is already in target channel
|
||||
* @param {MainServer} server Main server reference
|
||||
* @param {string} channel Target channel
|
||||
* @param {object} socket Target client to evaluate
|
||||
* @return {boolean||object}
|
||||
*/
|
||||
export function socketInChannel(server, channel, socket) {
|
||||
return findUser(server, {
|
||||
channel,
|
||||
userid: socket.userid,
|
||||
});
|
||||
}
|
49
commands/utility/_Constants.js
Normal file
49
commands/utility/_Constants.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Error ID list
|
||||
* @version 1.0.0
|
||||
* @description Exports an object that hold common global error IDs
|
||||
* @module Constants
|
||||
*/
|
||||
|
||||
/* Base error ranges */
|
||||
const GlobalErrors = 10;
|
||||
const JoinErrors = 20;
|
||||
const ChannelErrors = 30;
|
||||
const InviteErrors = 40;
|
||||
const SessionErrors = 50;
|
||||
|
||||
/**
|
||||
* Holds the numeric id values for each error type
|
||||
* @typedef {object} Errors
|
||||
*/
|
||||
export const Errors = {
|
||||
Global: {
|
||||
RATELIMIT: GlobalErrors + 1,
|
||||
UNKNOWN_USER: GlobalErrors + 2,
|
||||
PERMISSION: GlobalErrors + 3,
|
||||
},
|
||||
|
||||
Join: {
|
||||
RATELIMIT: JoinErrors + 1,
|
||||
INVALID_NICK: JoinErrors + 2,
|
||||
ALREADY_JOINED: JoinErrors + 3,
|
||||
NAME_TAKEN: JoinErrors + 4,
|
||||
},
|
||||
|
||||
Channel: {
|
||||
INVALID_NAME: ChannelErrors + 1,
|
||||
INVALID_LENGTH: ChannelErrors + 2,
|
||||
DEY_BANNED: ChannelErrors + 3,
|
||||
},
|
||||
|
||||
Invite: {
|
||||
RATELIMIT: InviteErrors + 1,
|
||||
},
|
||||
|
||||
Session: {
|
||||
BAD_SESSION: SessionErrors + 1,
|
||||
},
|
||||
};
|
||||
|
||||
export default Errors;
|
138
commands/utility/_LegacyFunctions.js
Normal file
138
commands/utility/_LegacyFunctions.js
Normal file
|
@ -0,0 +1,138 @@
|
|||
/* eslint no-param-reassign: 0 */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Support functions for old clients
|
||||
* @version 1.0.0
|
||||
* @description Functions to bridge the older v1 clients with the latest protocol
|
||||
* @module LegacyFunctions
|
||||
*/
|
||||
|
||||
import {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
} from './_UAC.js';
|
||||
|
||||
/**
|
||||
* Marks the socket as using the legacy protocol and
|
||||
* applies the missing `pass` property to the payload
|
||||
* @param {MainServer} server Main server reference
|
||||
* @param {WebSocket} socket Target client socket
|
||||
* @param {object} payload The original `join` payload
|
||||
* @returns {object}
|
||||
*/
|
||||
export function upgradeLegacyJoin(server, socket, payload) {
|
||||
const newPayload = payload;
|
||||
|
||||
// `join` is the legacy entry point, so apply protocol version
|
||||
socket.hcProtocol = 1;
|
||||
|
||||
// these would have been applied in the `session` module, apply it now
|
||||
socket.hash = server.getSocketHash(socket);
|
||||
socket.isBot = false;
|
||||
socket.color = false;
|
||||
|
||||
// pull the password from the nick
|
||||
const nickArray = payload.nick.split('#', 2);
|
||||
newPayload.nick = nickArray[0].trim();
|
||||
if (nickArray[1] && typeof payload.pass === 'undefined') {
|
||||
newPayload.pass = nickArray[1]; // eslint-disable-line prefer-destructuring
|
||||
}
|
||||
|
||||
// dunno how this happened on the legacy version
|
||||
if (typeof payload.password !== 'undefined') {
|
||||
newPayload.pass = payload.password;
|
||||
}
|
||||
|
||||
// apply the missing `userid` prop
|
||||
if (typeof socket.userid === 'undefined') {
|
||||
socket.userid = Math.floor(Math.random() * 9999999999999);
|
||||
}
|
||||
|
||||
return newPayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the correct `uType` label for the specific level
|
||||
* @param {number} level Numeric level to find the label for
|
||||
*/
|
||||
export function legacyLevelToLabel(level) {
|
||||
if (isAdmin(level)) return 'admin';
|
||||
if (isModerator(level)) return 'mod';
|
||||
|
||||
return 'user';
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the outgoing payload to an `info` cmd and add/change missing props
|
||||
* @param {object} payload Original payload
|
||||
* @param {string} nick Sender nick
|
||||
* @return {object}
|
||||
*/
|
||||
export function legacyInviteOut(payload, nick) {
|
||||
return {
|
||||
...payload,
|
||||
...{
|
||||
cmd: 'info',
|
||||
type: 'invite',
|
||||
from: nick,
|
||||
text: `${nick} invited you to ?${payload.inviteChannel}`,
|
||||
channel: payload.channel, // @todo Multichannel
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the outgoing payload to an `info` cmd and add/change missing props
|
||||
* @param {object} payload Original payload
|
||||
* @param {string} nick Receiver nick
|
||||
* @return {object}
|
||||
*/
|
||||
export function legacyInviteReply(payload, nick) {
|
||||
return {
|
||||
...payload,
|
||||
...{
|
||||
cmd: 'info',
|
||||
type: 'invite',
|
||||
from: '',
|
||||
text: `You invited ${nick} to ?${payload.inviteChannel}`,
|
||||
channel: payload.channel, // @todo Multichannel
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the outgoing payload to a `whisper` cmd and add/change missing props
|
||||
* @param {object} payload Original payload
|
||||
* @param {string} nick Sender nick
|
||||
* @return {object}
|
||||
*/
|
||||
export function legacyWhisperOut(payload, from) {
|
||||
return {
|
||||
...payload,
|
||||
...{
|
||||
cmd: 'info',
|
||||
type: 'whisper',
|
||||
from: from.nick,
|
||||
trip: from.trip || 'null',
|
||||
text: `${from.nick} whispered: ${payload.text}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the outgoing payload to a `whisper` cmd and add/change missing props
|
||||
* @param {object} payload Original payload
|
||||
* @param {string} nick Receiver nick
|
||||
* @return {object}
|
||||
*/
|
||||
export function legacyWhisperReply(payload, nick) {
|
||||
return {
|
||||
...payload,
|
||||
...{
|
||||
cmd: 'info',
|
||||
type: 'whisper',
|
||||
text: `You whispered to @${nick}: ${payload.text}`,
|
||||
},
|
||||
};
|
||||
}
|
31
commands/utility/_Text.js
Normal file
31
commands/utility/_Text.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
/* eslint import/prefer-default-export: 0 */
|
||||
|
||||
/**
|
||||
* @author MinusGix ( https://github.com/MinusGix )
|
||||
* @summary General string helper functions
|
||||
* @version v1.0.0
|
||||
* @description A library of several commonly used string functions
|
||||
* @module Text
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check and trim string provided by remote client
|
||||
* @public
|
||||
* @param {string} text - Subject string
|
||||
* @return {string|null}
|
||||
*/
|
||||
export const parseText = (text) => {
|
||||
// verifies user input is text
|
||||
if (typeof text !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
let sanitizedText = 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;
|
||||
};
|
|
@ -4,9 +4,17 @@
|
|||
* @property {Object} levels - Defines labels for default permission ranges
|
||||
* @author MinusGix ( https://github.com/MinusGix )
|
||||
* @version v1.0.0
|
||||
* @license WTFPL ( http://www.wtfpl.net/txt/copying/ )
|
||||
* @module UAC
|
||||
*/
|
||||
|
||||
import {
|
||||
getChannelSettings,
|
||||
} from './_Channels.js';
|
||||
|
||||
const {
|
||||
createHash,
|
||||
} = await import('crypto');
|
||||
|
||||
/**
|
||||
* Object defining labels for default permission ranges
|
||||
* @typedef {Object} levels
|
||||
|
@ -98,12 +106,15 @@ export function isTrustedUser(level) {
|
|||
*/
|
||||
export function getUserDetails(socket) {
|
||||
return {
|
||||
uType: socket.uType,
|
||||
nick: socket.nick,
|
||||
trip: socket.trip || 'null',
|
||||
trip: socket.trip || '',
|
||||
uType: socket.uType,
|
||||
hash: socket.hash,
|
||||
level: socket.level,
|
||||
userid: socket.userid,
|
||||
isBot: socket.isBot,
|
||||
color: socket.color,
|
||||
online: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -114,5 +125,67 @@ export function getUserDetails(socket) {
|
|||
* @return {boolean}
|
||||
*/
|
||||
export function verifyNickname(nick) {
|
||||
if (typeof nick === 'undefined') return false;
|
||||
|
||||
return /^[a-zA-Z0-9_]{1,24}$/.test(nick);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes a user's password, returning a trip code
|
||||
* or a blank string
|
||||
* @public
|
||||
* @param {string} pass User's password
|
||||
* @param {buffer} salt Server salt data
|
||||
* @param {string} config Server config object
|
||||
* @param {string} channel Channel-level permissions check
|
||||
* @return {string}
|
||||
*/
|
||||
export function getUserPerms(pass, salt, config, channel) {
|
||||
if (!pass) {
|
||||
return {
|
||||
trip: '',
|
||||
level: levels.default,
|
||||
};
|
||||
}
|
||||
|
||||
const sha = createHash('sha256');
|
||||
sha.update(pass + salt);
|
||||
const trip = sha.digest('base64').substr(0, 6);
|
||||
|
||||
// check if user is global admin
|
||||
if (trip === config.adminTrip) {
|
||||
return {
|
||||
trip: 'Admin',
|
||||
level: levels.admin,
|
||||
};
|
||||
}
|
||||
|
||||
let level = levels.default;
|
||||
|
||||
// check if user is global mod
|
||||
config.globalMods.forEach((mod) => { // eslint-disable-line consistent-return
|
||||
if (trip === mod.trip) {
|
||||
level = levels.moderator;
|
||||
}
|
||||
});
|
||||
|
||||
const channelSettings = getChannelSettings(config, channel);
|
||||
if (channelSettings.owned) {
|
||||
// check if user is channel owner
|
||||
// @todo channel ownership patch
|
||||
|
||||
// check if user is channel mod
|
||||
// @todo channel ownership patch
|
||||
|
||||
// check if user is channel trusted
|
||||
// @todo channel ownership patch
|
||||
}
|
||||
|
||||
// check if user is global trusted
|
||||
// @todo channel ownership patch
|
||||
|
||||
return {
|
||||
trip,
|
||||
level,
|
||||
};
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
You can programmatically access hack.chat using the following commands via a websocket. A list of wrappers written for accessing hack.chat can be found [here](https://github.com/hack-chat/3rd-party-software-list#libraries).
|
||||
|
||||
The commands are to be sent through a websocket to the URL `wss://hack.chat/chat-ws` (everything sent and received are JSON). If you are sending messages locally or to another domain, replace 'hack.chat' with the respective domain. If you're running your own instance of hack.chat, you can retain backwards-compatibility in order to ensure that software created for the main server will work on yours too.
|
||||
|
||||
All commands sent must be JSON objects with the command specified in the `"cmd"` key. For example:
|
||||
```json
|
||||
{
|
||||
"cmd": "join",
|
||||
"channel": "programming",
|
||||
"nick": "john#doe"
|
||||
}
|
||||
```
|
||||
|
||||
hack.chat has three permission levels. When you access a command, hack.chat automatically knows your permission level from your trip code. The lowest permission level is `user`. `mod` is above `user`, so it can access `user` commands in addition to `mod` commands. `admin` is similarly above `mod`.
|
||||
|
||||
# `user`
|
||||
|
||||
|Command|Parameters|Explanation|
|
||||
|-------|----------|-----------|
|
||||
|`changenick`|`nick`|Changes the current connection's nickname.|
|
||||
|`chat`|`text`|This broadcasts `text` to the channel the user is connected to.|
|
||||
|`disconnect`||An event handler or forced disconnect.|
|
||||
|`invite`|`nick`|Generates a pseudo-unique channel name and passes it to both the calling user and `nick`.|
|
||||
|`join`|`channel`, `nick`|Places the calling socket into the target channel with the target nick and broadcasts the event to the channel.|
|
||||
|`morestats`||Sends back the current server's stats to the calling client.|
|
||||
|`move`|`channel`|This will change the current channel to `channel`.|
|
||||
|`stats`||Sends back legacy server stats to the calling client. Use `morestats` when possible.|
|
||||
|`help`|`category` or `command`|Gives documentation programmatically. If `category` (the permission level, such as `mod`) is sent, a list of commands available to that permission level will be sent back (as a `string` and not an `array`). This list only includes what is unique to that category and not every command a user with that permission level could perform. If `command` (e.g., `chat`) is sent, a description of the command will be sent back.|
|
||||
|
||||
# `mod`
|
||||
|
||||
|Command|Parameters|Explanation|
|
||||
|-------|----------|-----------|
|
||||
|`ban`|`nick`|Disconnects the target nickname in the same channel as the calling socket and adds it to the rate limiter.|
|
||||
|`kick`|`nick`|Silently forces target client(s) into another channel. `nick` may be `string` or `array` of `string`s.|
|
||||
|`unban`|`ip` or `hash`|Removes the target ip from the rate limiter.|
|
||||
|`dumb`|`nick`|Mutes a user's (spammer's) texts such that it is displayable to the user only.|
|
||||
|`speak`|`ip` or `hash`|Unmutes the user's (spammer's) texts and makes it displayable to everyone again.|
|
||||
|
||||
# `admin`
|
||||
|
||||
|Command|Parameters|Explanation|
|
||||
|-------|----------|-----------|
|
||||
|`addmod`|`nick`|Adds the target trip to the config as a mod and upgrades the socket type.|
|
||||
|`listusers`||Outputs all current channels and sockets in those channels.|
|
||||
|`reload`||(Re)loads any new commands into memory and outputs errors, if any.|
|
||||
|`saveconfig`||Saves the current config.|
|
||||
|`shout`|`text`|Displays the passed text to each client connected.|
|
159
documentation/admin_addmod.js.html
Normal file
159
documentation/admin_addmod.js.html
Normal file
|
@ -0,0 +1,159 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: admin/addmod.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: admin/addmod.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Create a new mod trip
|
||||
* @version 1.0.0
|
||||
* @description Adds target trip to the config as a mod and upgrades the socket type
|
||||
* @module addmod
|
||||
*/
|
||||
|
||||
import {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
levels,
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (!isAdmin(socket.level)) {
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// add new trip to config
|
||||
core.appConfig.data.globalMods.push({ trip: payload.trip });
|
||||
|
||||
// find targets current connections
|
||||
const newMod = server.findSockets({ trip: payload.trip });
|
||||
if (newMod.length !== 0) {
|
||||
// build update notice with new privileges
|
||||
const updateNotice = {
|
||||
...getUserDetails(newMod[0]),
|
||||
...{
|
||||
cmd: 'updateUser',
|
||||
uType: 'mod', // @todo use legacyLevelToLabel from _LegacyFunctions.js
|
||||
level: levels.moderator,
|
||||
},
|
||||
};
|
||||
|
||||
for (let i = 0, l = newMod.length; i < l; i += 1) {
|
||||
// upgrade privileges
|
||||
newMod[i].uType = 'mod'; // @todo use legacyLevelToLabel from _LegacyFunctions.js
|
||||
newMod[i].level = levels.moderator;
|
||||
|
||||
// inform new mod
|
||||
server.send({
|
||||
cmd: 'info',
|
||||
text: 'You are now a mod.',
|
||||
channel: newMod[i].channel, // @todo Multichannel
|
||||
}, newMod[i]);
|
||||
|
||||
// notify channel
|
||||
server.broadcast({
|
||||
...updateNotice,
|
||||
...{
|
||||
channel: newMod[i].channel,
|
||||
},
|
||||
}, { channel: newMod[i].channel });
|
||||
}
|
||||
}
|
||||
|
||||
// return success message
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `Added mod trip: ${payload.trip}, remember to run 'saveconfig' to make it permanent`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
// notify all mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Added mod: ${payload.trip}`,
|
||||
channel: false, // @todo Multichannel, false for global info
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "trip"
|
||||
* @public
|
||||
* @typedef {Array} addmod/requiredData
|
||||
*/
|
||||
export const requiredData = ['trip'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} addmod/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'addmod',
|
||||
category: 'admin',
|
||||
description: 'Adds target trip to the config as a mod and upgrades the socket type',
|
||||
usage: `
|
||||
API: { cmd: 'addmod', trip: '<target trip>' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
128
documentation/admin_listusers.js.html
Normal file
128
documentation/admin_listusers.js.html
Normal file
|
@ -0,0 +1,128 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: admin/listusers.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: admin/listusers.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/* eslint no-unused-vars: 0 */
|
||||
/* eslint no-restricted-syntax: 0 */
|
||||
/* eslint guard-for-in: 0 */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Show users and channels
|
||||
* @version 1.0.0
|
||||
* @description Outputs all current channels and sockets in those channels
|
||||
* @module listusers
|
||||
*/
|
||||
|
||||
import {
|
||||
isAdmin,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ server, socket }) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (!isAdmin(socket.level)) {
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// find all users currently in a channel
|
||||
const currentUsers = server.findSockets({
|
||||
channel: (channel) => true,
|
||||
});
|
||||
|
||||
// compile channel and user list
|
||||
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}`,
|
||||
);
|
||||
}
|
||||
|
||||
// build output
|
||||
const lines = [];
|
||||
for (const channel in channels) {
|
||||
lines.push(`?${channel} ${channels[channel].join(', ')}`);
|
||||
}
|
||||
|
||||
// send reply
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: lines.join('\n'),
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} listusers/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'listusers',
|
||||
category: 'admin',
|
||||
description: 'Outputs all current channels and sockets in those channels',
|
||||
usage: `
|
||||
API: { cmd: 'listusers' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
122
documentation/admin_reload.js.html
Normal file
122
documentation/admin_reload.js.html
Normal file
|
@ -0,0 +1,122 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: admin/reload.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: admin/reload.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Refresh modules
|
||||
* @version 1.0.0
|
||||
* @description Allows a remote user to clear and re-import the server command modules
|
||||
* @module reload
|
||||
*/
|
||||
|
||||
import {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (!isAdmin(socket.level)) {
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// do command reload and store results
|
||||
let loadResult = await core.commands.reloadCommands();
|
||||
|
||||
// clear and rebuild all module hooks
|
||||
server.loadHooks();
|
||||
|
||||
// build reply based on reload results
|
||||
if (loadResult === '') {
|
||||
loadResult = `Reloaded ${core.commands.commands.length} commands, 0 errors`;
|
||||
} else {
|
||||
loadResult = `Reloaded ${core.commands.commands.length} commands, error(s):
|
||||
${loadResult}`;
|
||||
}
|
||||
|
||||
if (typeof payload.reason !== 'undefined') {
|
||||
loadResult += `\nReason: ${payload.reason}`;
|
||||
}
|
||||
|
||||
// send results to moderators (which the user using this command is higher than)
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: loadResult,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} reload/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'reload',
|
||||
category: 'admin',
|
||||
description: 'Allows a remote user to clear and re-import the server command modules',
|
||||
usage: `
|
||||
API: { cmd: 'reload', reason: '<optional reason append>' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
164
documentation/admin_removemod.js.html
Normal file
164
documentation/admin_removemod.js.html
Normal file
|
@ -0,0 +1,164 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: admin/removemod.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: admin/removemod.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Removes a mod
|
||||
* @version 1.0.0
|
||||
* @description Removes target trip from the config as a mod and downgrades the socket type
|
||||
* @module removemod
|
||||
*/
|
||||
|
||||
import {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
levels,
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (!isAdmin(socket.level)) {
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// remove trip from config
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
core.appConfig.data.globalMods = core.appConfig.data.globalMods.filter(
|
||||
(mod) => mod.trip !== payload.trip,
|
||||
);
|
||||
|
||||
// find targets current connections
|
||||
const targetMod = server.findSockets({ trip: payload.trip });
|
||||
if (targetMod.length !== 0) {
|
||||
// build update notice with new privileges
|
||||
const updateNotice = {
|
||||
...getUserDetails(targetMod[0]),
|
||||
...{
|
||||
cmd: 'updateUser',
|
||||
uType: 'user', // @todo use legacyLevelToLabel from _LegacyFunctions.js
|
||||
level: levels.default,
|
||||
},
|
||||
};
|
||||
|
||||
for (let i = 0, l = targetMod.length; i < l; i += 1) {
|
||||
// downgrade privileges
|
||||
targetMod[i].uType = 'user';
|
||||
targetMod[i].level = levels.default;
|
||||
|
||||
// inform ex-mod
|
||||
server.send({
|
||||
cmd: 'info',
|
||||
text: 'You are now a user.',
|
||||
channel: targetMod[i].channel, // @todo Multichannel
|
||||
}, targetMod[i]);
|
||||
|
||||
// notify channel
|
||||
server.broadcast({
|
||||
...updateNotice,
|
||||
...{
|
||||
channel: targetMod[i].channel,
|
||||
},
|
||||
}, { channel: targetMod[i].channel });
|
||||
}
|
||||
}
|
||||
|
||||
// return success message
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `Removed mod trip: ${
|
||||
payload.trip
|
||||
}, remember to run 'saveconfig' to make it permanent`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
// notify all mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Removed mod: ${payload.trip}`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "trip"
|
||||
* @public
|
||||
* @typedef {Array} removemod/requiredData
|
||||
*/
|
||||
export const requiredData = ['trip'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} removemod/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'removemod',
|
||||
category: 'admin',
|
||||
description: 'Removes target trip from the config as a mod and downgrades the socket type',
|
||||
usage: `
|
||||
API: { cmd: 'removemod', trip: '<target trip>' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
113
documentation/admin_saveconfig.js.html
Normal file
113
documentation/admin_saveconfig.js.html
Normal file
|
@ -0,0 +1,113 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: admin/saveconfig.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: admin/saveconfig.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Saves the config
|
||||
* @version 1.0.0
|
||||
* @description Writes the current config to disk
|
||||
* @module saveconfig
|
||||
*/
|
||||
|
||||
import {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ core, server, socket }) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (!isAdmin(socket.level)) {
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// attempt save, notify of failure
|
||||
try {
|
||||
await core.appConfig.write();
|
||||
} catch (err) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Failed to save config, check logs.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// return success message to moderators and admins
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: 'Config saved!',
|
||||
channel: false, // @todo Multichannel
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} saveconfig/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'saveconfig',
|
||||
category: 'admin',
|
||||
description: 'Writes the current config to disk',
|
||||
usage: `
|
||||
API: { cmd: 'saveconfig' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
109
documentation/admin_shout.js.html
Normal file
109
documentation/admin_shout.js.html
Normal file
|
@ -0,0 +1,109 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: admin/shout.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: admin/shout.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Emit text everywhere
|
||||
* @version 1.0.0
|
||||
* @description Displays passed text to every client connected
|
||||
* @module shout
|
||||
*/
|
||||
|
||||
import {
|
||||
isAdmin,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ server, socket, payload }) {
|
||||
// increase rate limit chance and ignore if not admin
|
||||
if (!isAdmin(socket.level)) {
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// send text to all channels
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Server Notice: ${payload.text}`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, {});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "text"
|
||||
* @public
|
||||
* @typedef {Array} shout/requiredData
|
||||
*/
|
||||
export const requiredData = ['text'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} shout/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'shout',
|
||||
category: 'admin',
|
||||
description: 'Displays passed text to every client connected',
|
||||
usage: `
|
||||
API: { cmd: 'shout', text: '<shout text>' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
209
documentation/core_changecolor.js.html
Normal file
209
documentation/core_changecolor.js.html
Normal file
|
@ -0,0 +1,209 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: core/changecolor.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: core/changecolor.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Update name color
|
||||
* @version 1.0.0
|
||||
* @description Allows calling client to change their nickname color
|
||||
* @module changecolor
|
||||
*/
|
||||
|
||||
import {
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Validate a string as a valid hex color string
|
||||
* @param {string} color - Color string to validate
|
||||
* @private
|
||||
* @todo Move into utility module
|
||||
* @return {boolean}
|
||||
*/
|
||||
const verifyColor = (color) => /(^[0-9A-F]{6}$)|(^[0-9A-F]{3}$)/i.test(color);
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
server, socket, payload,
|
||||
}) {
|
||||
const { channel } = socket;
|
||||
|
||||
if (server.police.frisk(socket, 1)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are changing colors too fast. Wait a moment before trying again.',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// verify user data is string
|
||||
if (typeof payload.color !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure requested nickname meets standards
|
||||
const newColor = payload.color.trim().toUpperCase().replace(/#/g, '');
|
||||
if (newColor !== 'RESET' && !verifyColor(newColor)) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Invalid color! Color must be in hex value',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
if (newColor === 'RESET') {
|
||||
socket.color = false; // eslint-disable-line no-param-reassign
|
||||
} else {
|
||||
socket.color = newColor; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
// build update notice with new color
|
||||
const updateNotice = {
|
||||
...getUserDetails(socket),
|
||||
...{
|
||||
cmd: 'updateUser',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
},
|
||||
};
|
||||
|
||||
// notify channel that the user has changed their name
|
||||
// @todo this should be sent to every channel the user is in (multichannel)
|
||||
server.broadcast(updateNotice, { channel: socket.channel });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.colorCheck.bind(this), 29);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function colorCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/color ')) {
|
||||
const input = payload.text.split(' ');
|
||||
|
||||
// If there is no color target parameter
|
||||
if (input[1] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Refer to `/help changecolor` for instructions on how to use this command.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'changecolor',
|
||||
color: input[1],
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "color"
|
||||
* @public
|
||||
* @typedef {Array} changecolor/requiredData
|
||||
*/
|
||||
export const requiredData = ['color'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} changecolor/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'changecolor',
|
||||
category: 'core',
|
||||
description: 'Allows calling client to change their nickname color',
|
||||
usage: `
|
||||
API: { cmd: 'changecolor', color: '<new color as hex>' }
|
||||
Text: /color <new color as hex>
|
||||
Removal: /color reset`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
264
documentation/core_changenick.js.html
Normal file
264
documentation/core_changenick.js.html
Normal file
|
@ -0,0 +1,264 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: core/changenick.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: core/changenick.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/* eslint eqeqeq: 0 */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Update nickname
|
||||
* @version 1.0.0
|
||||
* @description Allows calling client to change their current nickname
|
||||
* @module changenick
|
||||
*/
|
||||
|
||||
import {
|
||||
verifyNickname,
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
server, socket, payload,
|
||||
}) {
|
||||
const { channel } = socket;
|
||||
|
||||
if (server.police.frisk(socket, 6)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are changing nicknames too fast. Wait a moment before trying again.',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// verify user data is string
|
||||
if (typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const previousNick = socket.nick;
|
||||
|
||||
// make sure requested nickname meets standards
|
||||
const newNick = payload.nick.trim();
|
||||
if (!verifyNickname(newNick)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Nickname must consist of up to 24 letters, numbers, and underscores',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
if (newNick == previousNick) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You already have that name',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// find any sockets that have the same nickname
|
||||
const userExists = server.findSockets({
|
||||
channel,
|
||||
nick: (targetNick) => targetNick.toLowerCase() === newNick.toLowerCase()
|
||||
// Allow them to rename themselves to a different case
|
||||
&& targetNick != previousNick,
|
||||
});
|
||||
|
||||
// return error if found
|
||||
if (userExists.length > 0) {
|
||||
// That nickname is already in that channel
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Nickname taken',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// build update notice with new nickname
|
||||
const updateNotice = {
|
||||
...getUserDetails(socket),
|
||||
...{
|
||||
cmd: 'updateUser',
|
||||
nick: newNick,
|
||||
channel, // @todo Multichannel
|
||||
},
|
||||
};
|
||||
|
||||
// build join and leave notices for legacy clients
|
||||
const leaveNotice = {
|
||||
cmd: 'onlineRemove',
|
||||
userid: socket.userid,
|
||||
nick: socket.nick,
|
||||
channel, // @todo Multichannel
|
||||
};
|
||||
|
||||
const joinNotice = {
|
||||
...getUserDetails(socket),
|
||||
...{
|
||||
cmd: 'onlineAdd',
|
||||
nick: newNick,
|
||||
channel, // @todo Multichannel
|
||||
},
|
||||
};
|
||||
|
||||
// gather channel peers
|
||||
const peerList = server.findSockets({ channel });
|
||||
for (let i = 0, l = peerList.length; i < l; i += 1) {
|
||||
if (peerList[i].hcProtocol === 1) {
|
||||
// send join/leave to legacy clients
|
||||
server.send(leaveNotice, peerList[i]);
|
||||
server.send(joinNotice, peerList[i]);
|
||||
} else {
|
||||
// send update info
|
||||
// @todo this should be sent to every channel the client is in (multichannel)
|
||||
server.send(updateNotice, peerList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// notify channel that the user has changed their name
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick} is now ${newNick}`,
|
||||
channel, // @todo Multichannel
|
||||
}, { channel });
|
||||
|
||||
// commit change to nickname
|
||||
socket.nick = newNick; // eslint-disable-line no-param-reassign
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.nickCheck.bind(this), 29);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function nickCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/nick')) {
|
||||
const input = payload.text.split(' ');
|
||||
|
||||
// If there is no nickname target parameter
|
||||
if (!input[1]) {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Refer to `/help nick` for instructions on how to use this command.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const newNick = input[1].replace(/@/g, '');
|
||||
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'changenick',
|
||||
nick: newNick,
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "nick"
|
||||
* @public
|
||||
* @typedef {Array} changenick/requiredData
|
||||
*/
|
||||
export const requiredData = ['nick'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} changenick/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'changenick',
|
||||
category: 'core',
|
||||
description: 'Allows calling client to change their current nickname',
|
||||
usage: `
|
||||
API: { cmd: 'changenick', nick: '<new nickname>' }
|
||||
Text: /nick <new nickname>`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
296
documentation/core_chat.js.html
Normal file
296
documentation/core_chat.js.html
Normal file
|
@ -0,0 +1,296 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: core/chat.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: core/chat.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Send chat messages
|
||||
* @version 1.0.0
|
||||
* @description Broadcasts passed `text` field to the calling users channel
|
||||
* @module chat
|
||||
*/
|
||||
|
||||
import {
|
||||
parseText,
|
||||
} from '../utility/_Text.js';
|
||||
import {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Maximum length of the customId property
|
||||
* @type {number}
|
||||
*/
|
||||
export const MAX_MESSAGE_ID_LENGTH = 6;
|
||||
|
||||
/**
|
||||
* The time in milliseconds before a message is considered stale, and thus no longer allowed
|
||||
* to be edited.
|
||||
* @type {number}
|
||||
*/
|
||||
const ACTIVE_TIMEOUT = 5 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* The time in milliseconds that a check for stale messages should be performed.
|
||||
* @type {number}
|
||||
*/
|
||||
const TIMEOUT_CHECK_INTERVAL = 30 * 1000;
|
||||
|
||||
/**
|
||||
* Stores active messages that can be edited.
|
||||
* @type {Array}
|
||||
*/
|
||||
export const ACTIVE_MESSAGES = [];
|
||||
|
||||
/**
|
||||
* Cleans up stale messages.
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function cleanActiveMessages() {
|
||||
const now = Date.now();
|
||||
for (let i = 0; i < ACTIVE_MESSAGES.length; i += 1) {
|
||||
const message = ACTIVE_MESSAGES[i];
|
||||
if (now - message.sent > ACTIVE_TIMEOUT || message.toDelete) {
|
||||
ACTIVE_MESSAGES.splice(i, 1);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This won't get cleared on module reload.
|
||||
setInterval(cleanActiveMessages, TIMEOUT_CHECK_INTERVAL);
|
||||
|
||||
/**
|
||||
* Adds a message to the active messages map.
|
||||
* @public
|
||||
* @param {string} id
|
||||
* @param {number} userid
|
||||
* @return {void}
|
||||
*/
|
||||
export function addActiveMessage(customId, userid) {
|
||||
ACTIVE_MESSAGES.push({
|
||||
customId,
|
||||
userid,
|
||||
sent: Date.now(),
|
||||
toDelete: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// check user input
|
||||
const text = parseText(payload.text);
|
||||
|
||||
if (!text) {
|
||||
// lets not send objects or empty text, yea?
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
// check for spam
|
||||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket, score)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
const { customId } = payload;
|
||||
|
||||
if (typeof (customId) === 'string' && customId.length > MAX_MESSAGE_ID_LENGTH) {
|
||||
// There's a limit on the custom id length.
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
// build chat payload
|
||||
const outgoingPayload = {
|
||||
cmd: 'chat',
|
||||
nick: socket.nick, /* @legacy */
|
||||
uType: socket.uType, /* @legacy */
|
||||
userid: socket.userid,
|
||||
channel: socket.channel,
|
||||
text,
|
||||
level: socket.level,
|
||||
customId,
|
||||
};
|
||||
|
||||
if (isAdmin(socket.level)) {
|
||||
outgoingPayload.admin = true;
|
||||
} else if (isModerator(socket.level)) {
|
||||
outgoingPayload.mod = true;
|
||||
}
|
||||
|
||||
if (socket.trip) {
|
||||
outgoingPayload.trip = socket.trip; /* @legacy */
|
||||
}
|
||||
|
||||
if (socket.color) {
|
||||
outgoingPayload.color = socket.color;
|
||||
}
|
||||
|
||||
addActiveMessage(outgoingPayload.customId, socket.userid);
|
||||
|
||||
// broadcast to channel peers
|
||||
server.broadcast(outgoingPayload, { channel: socket.channel });
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('messages-sent');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.commandCheckIn.bind(this), 20);
|
||||
server.registerHook('in', 'chat', this.finalCmdCheck.bind(this), 254);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* checks for miscellaneous '/' based commands
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function commandCheckIn({ server, socket, payload }) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/myhash')) {
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `Your hash: ${socket.hash}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* assumes a failed chat command invocation and will reject with notice
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function finalCmdCheck({ server, socket, payload }) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!payload.text.startsWith('/')) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('//')) {
|
||||
payload.text = payload.text.substr(1); // eslint-disable-line no-param-reassign
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: `Unknown command: ${payload.text}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "text"
|
||||
* @public
|
||||
* @typedef {Array} chat/requiredData
|
||||
*/
|
||||
export const requiredData = ['text'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} chat/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'chat',
|
||||
category: 'core',
|
||||
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`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
213
documentation/core_emote.js.html
Normal file
213
documentation/core_emote.js.html
Normal file
|
@ -0,0 +1,213 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: core/emote.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: core/emote.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Emote / action text
|
||||
* @version 1.0.0
|
||||
* @description Broadcasts an emote to the current channel
|
||||
* @module emote
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check and trim string provided by remote client
|
||||
* @param {string} text - Subject string
|
||||
* @private
|
||||
* @todo Move into utility module
|
||||
* @return {string|boolean}
|
||||
*/
|
||||
const parseText = (text) => {
|
||||
// verifies user input is text
|
||||
if (typeof text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let sanitizedText = 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ server, socket, payload }) {
|
||||
// check user input
|
||||
let text = parseText(payload.text);
|
||||
|
||||
if (!text) {
|
||||
// lets not send objects or empty text, yea?
|
||||
return server.police.frisk(socket, 8);
|
||||
}
|
||||
|
||||
// check for spam
|
||||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket, score)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
if (!text.startsWith("'")) {
|
||||
text = ` ${text}`;
|
||||
}
|
||||
|
||||
const newPayload = {
|
||||
cmd: 'emote',
|
||||
nick: socket.nick,
|
||||
userid: socket.userid,
|
||||
text: `@${socket.nick}${text}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
};
|
||||
|
||||
if (socket.trip) {
|
||||
newPayload.trip = socket.trip;
|
||||
}
|
||||
|
||||
// broadcast to channel peers
|
||||
server.broadcast(newPayload, { channel: socket.channel });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.emoteCheck.bind(this), 30);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hooks chat commands checking for /me
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function emoteCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/me ')) {
|
||||
const input = payload.text.split(' ');
|
||||
|
||||
// If there is no emote target parameter
|
||||
if (input[1] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Refer to `/help emote` for instructions on how to use this command.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
input.splice(0, 1);
|
||||
const actionText = input.join(' ');
|
||||
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'emote',
|
||||
text: actionText,
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "text"
|
||||
* @public
|
||||
* @typedef {Array} emote/requiredData
|
||||
*/
|
||||
export const requiredData = ['text'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} emote/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'emote',
|
||||
category: 'core',
|
||||
description: 'Broadcasts an emote to the current channel',
|
||||
usage: `
|
||||
API: { cmd: 'emote', text: '<emote/action text>' }
|
||||
Text: /me <emote/action text>`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
186
documentation/core_help.js.html
Normal file
186
documentation/core_help.js.html
Normal file
|
@ -0,0 +1,186 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: core/help.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: core/help.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Get help
|
||||
* @version 1.0.0
|
||||
* @description Outputs information about the servers current protocol
|
||||
* @module help
|
||||
*/
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// check for spam
|
||||
if (server.police.frisk(socket, 2)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// verify user input
|
||||
if (typeof payload.command !== 'undefined' && typeof payload.command !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
let reply = '';
|
||||
if (typeof payload.command === 'undefined') {
|
||||
reply += '# All commands:\n|Category:|Name:|\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())}:|`;
|
||||
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`;
|
||||
}
|
||||
|
||||
reply += '---\nFor specific help on certain commands, use either:\nText: `/help <command name>`\nAPI: `{cmd: \'help\', command: \'<command name>\'}`';
|
||||
} else {
|
||||
const command = core.commands.get(payload.command);
|
||||
|
||||
if (typeof command === 'undefined') {
|
||||
reply += 'Unknown command';
|
||||
} else {
|
||||
reply += `# ${command.info.name} command:\n| | |\n|---:|---|\n`;
|
||||
reply += `|**Name:**|${command.info.name}|\n`;
|
||||
reply += `|**Hash:**|${command.info.srcHash}|\n`;
|
||||
reply += `|**Aliases:**|${typeof command.info.aliases !== 'undefined' ? command.info.aliases.join(', ') : 'None'}|\n`;
|
||||
reply += `|**Category:**|${command.info.category.replace('../src/commands/', '').replace(/^\w/, (c) => c.toUpperCase())}|\n`;
|
||||
reply += `|**Required Parameters:**|${command.requiredData || 'None'}|\n`;
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
reply += `|**Description:**|${command.info.description || '¯\_(ツ)_/¯'}|\n\n`;
|
||||
reply += `**Usage:** ${command.info.usage || command.info.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
// output reply
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: reply,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.helpCheck.bind(this), 28);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hooks chat commands checking for /help
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function helpCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/help')) {
|
||||
const input = payload.text.substr(1).split(' ', 2);
|
||||
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: input[0],
|
||||
command: input[1],
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} help/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'help',
|
||||
category: 'core',
|
||||
description: 'Outputs information about the servers current protocol',
|
||||
usage: `
|
||||
API: { cmd: 'help', command: '<optional command name>' }
|
||||
Text: /help <optional command name>`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
174
documentation/core_invite.js.html
Normal file
174
documentation/core_invite.js.html
Normal file
|
@ -0,0 +1,174 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: core/invite.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: core/invite.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Send an invite
|
||||
* @version 1.0.0
|
||||
* @description Sends an invite to the target client with the provided channel, or a random channel
|
||||
* @module invite
|
||||
*/
|
||||
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
legacyInviteOut,
|
||||
legacyInviteReply,
|
||||
} from '../utility/_LegacyFunctions.js';
|
||||
|
||||
/**
|
||||
* Returns the channel that should be invited to.
|
||||
* @param {any} channel
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
export function getChannel(channel = undefined) {
|
||||
if (typeof channel === 'string') {
|
||||
return channel;
|
||||
}
|
||||
return Math.random().toString(36).substr(2, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// check for spam
|
||||
if (server.police.frisk(socket, 2)) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You are sending invites too fast. Wait a moment before trying again.',
|
||||
id: Errors.Invite.RATELIMIT,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// verify user input
|
||||
// if this is a legacy client add missing params to payload
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof socket.channel === 'undefined' || typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
} else if (typeof payload.userid !== 'number' || typeof payload.channel !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// @todo Verify this socket is part of payload.channel - multichannel patch
|
||||
// find target user
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// generate common channel
|
||||
const channel = getChannel(payload.to);
|
||||
|
||||
// build invite
|
||||
const outgoingPayload = {
|
||||
cmd: 'invite',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
from: socket.userid,
|
||||
to: targetUser.userid,
|
||||
inviteChannel: channel,
|
||||
};
|
||||
|
||||
// send invite notice to target client
|
||||
if (targetUser.hcProtocol === 1) {
|
||||
server.reply(legacyInviteOut(outgoingPayload, socket.nick), targetUser);
|
||||
} else {
|
||||
server.reply(outgoingPayload, targetUser);
|
||||
}
|
||||
|
||||
// send invite notice to this client
|
||||
if (socket.hcProtocol === 1) {
|
||||
server.reply(legacyInviteReply(outgoingPayload, targetUser.nick), socket);
|
||||
} else {
|
||||
server.reply(outgoingPayload, socket);
|
||||
}
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('invites-sent');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} invite/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'invite',
|
||||
category: 'core',
|
||||
description: 'Sends an invite to the target client with the provided channel, or a random channel.',
|
||||
usage: `
|
||||
API: { cmd: 'invite', nick: '<target nickname>', to: '<optional destination channel>' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
329
documentation/core_join.js.html
Normal file
329
documentation/core_join.js.html
Normal file
|
@ -0,0 +1,329 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: core/join.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: core/join.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/* eslint no-param-reassign: 0 */
|
||||
/* eslint import/no-cycle: [0, { ignoreExternal: true }] */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Join target channel
|
||||
* @version 1.0.0
|
||||
* @description Join the target channel using the supplied nick and password
|
||||
* @module join
|
||||
*/
|
||||
|
||||
import {
|
||||
getSession,
|
||||
} from './session.js';
|
||||
import {
|
||||
canJoinChannel,
|
||||
socketInChannel,
|
||||
} from '../utility/_Channels.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
upgradeLegacyJoin,
|
||||
legacyLevelToLabel,
|
||||
} from '../utility/_LegacyFunctions.js';
|
||||
import {
|
||||
verifyNickname,
|
||||
getUserPerms,
|
||||
getUserDetails,
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// check for spam
|
||||
if (server.police.frisk(socket, 3)) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You are joining channels too fast. Wait a moment and try again.',
|
||||
id: Errors.Join.RATELIMIT,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// `join` is the legacy entry point, check if it needs to be upgraded
|
||||
if (typeof socket.hcProtocol === 'undefined' || socket.hcProtocol === 1) {
|
||||
payload = upgradeLegacyJoin(server, socket, payload);
|
||||
}
|
||||
|
||||
// store payload values
|
||||
const { channel, nick, pass } = payload;
|
||||
|
||||
// check if a client is able to join target channel
|
||||
const mayJoin = canJoinChannel(channel, socket);
|
||||
if (mayJoin !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You may not join that channel.',
|
||||
id: mayJoin,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// calling socket already in a channel
|
||||
// @todo multichannel update, will remove
|
||||
if (typeof socket.channel !== 'undefined') {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove this
|
||||
text: 'Joining more than one channel is not currently supported',
|
||||
id: Errors.Join.ALREADY_JOINED,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
// end todo
|
||||
|
||||
// validates the user input for `nick`
|
||||
if (verifyNickname(nick, socket) !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Nickname must consist of up to 24 letters, numbers, and underscores',
|
||||
id: Errors.Join.INVALID_NICK,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// get trip and level
|
||||
const { trip, level } = getUserPerms(pass, core.saltKey, core.appConfig.data, channel);
|
||||
|
||||
// store the user values
|
||||
const userInfo = {
|
||||
nick,
|
||||
trip,
|
||||
uType: legacyLevelToLabel(level),
|
||||
hash: socket.hash,
|
||||
level,
|
||||
userid: socket.userid,
|
||||
isBot: socket.isBot,
|
||||
color: socket.color,
|
||||
channel,
|
||||
};
|
||||
|
||||
// check if the nickname already exists in the channel
|
||||
const userExists = server.findSockets({
|
||||
channel,
|
||||
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',
|
||||
id: Errors.Join.NAME_TAKEN,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// prepare to notify channel peers
|
||||
const newPeerList = server.findSockets({ channel });
|
||||
const nicks = []; /* @legacy */
|
||||
const users = [];
|
||||
const joinAnnouncement = { ...{ cmd: 'onlineAdd' }, ...userInfo };
|
||||
|
||||
// send join announcement and prep online set reply
|
||||
for (let i = 0, l = newPeerList.length; i < l; i += 1) {
|
||||
server.reply(joinAnnouncement, newPeerList[i]);
|
||||
|
||||
nicks.push(newPeerList[i].nick); /* @legacy */
|
||||
users.push({
|
||||
...{
|
||||
channel,
|
||||
isme: false,
|
||||
},
|
||||
...getUserDetails(newPeerList[i]),
|
||||
});
|
||||
}
|
||||
|
||||
// store user info
|
||||
socket.nick = userInfo.nick;
|
||||
socket.trip = userInfo.trip;
|
||||
socket.level = userInfo.level;
|
||||
socket.uType = userInfo.uType; /* @legacy */
|
||||
socket.channel = channel; /* @legacy */
|
||||
// @todo multi-channel patch
|
||||
// socket.channels.push(channel);
|
||||
socket.channels = [channel];
|
||||
|
||||
// global mod perks
|
||||
if (isModerator(socket.level)) {
|
||||
socket.ratelimitImmune = true;
|
||||
}
|
||||
|
||||
nicks.push(userInfo.nick); /* @legacy */
|
||||
users.push({ ...{ isme: true, isBot: socket.isBot }, ...userInfo });
|
||||
|
||||
// reply with channel peer list
|
||||
server.reply({
|
||||
cmd: 'onlineSet',
|
||||
nicks, /* @legacy */
|
||||
users,
|
||||
channel, // @todo Multichannel (?)
|
||||
}, socket);
|
||||
|
||||
// update client with new session info
|
||||
server.reply({
|
||||
cmd: 'session',
|
||||
restored: false,
|
||||
token: getSession(socket, core),
|
||||
channels: socket.channels,
|
||||
}, socket);
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('users-joined');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function restoreJoin({
|
||||
server, socket, channel,
|
||||
}) {
|
||||
// check if a client is able to join target channel
|
||||
const mayJoin = canJoinChannel(channel, socket);
|
||||
if (mayJoin !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You may not join that channel.',
|
||||
id: mayJoin,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// store the user values
|
||||
const userInfo = {
|
||||
nick: socket.nick,
|
||||
trip: socket.trip,
|
||||
uType: legacyLevelToLabel(socket.level),
|
||||
hash: socket.hash,
|
||||
level: socket.level,
|
||||
userid: socket.userid,
|
||||
isBot: socket.isBot,
|
||||
color: socket.color,
|
||||
channel,
|
||||
};
|
||||
|
||||
// prepare to notify channel peers
|
||||
const newPeerList = server.findSockets({ channel });
|
||||
const nicks = []; /* @legacy */
|
||||
const users = [];
|
||||
const joinAnnouncement = { ...{ cmd: 'onlineAdd' }, ...userInfo };
|
||||
// build update notice with new privileges
|
||||
const updateAnnouncement = {
|
||||
...getUserDetails(socket),
|
||||
...{
|
||||
cmd: 'updateUser',
|
||||
online: true,
|
||||
},
|
||||
};
|
||||
|
||||
const isDuplicate = socketInChannel(server, channel, socket);
|
||||
|
||||
// send join announcement and prep online set reply
|
||||
for (let i = 0, l = newPeerList.length; i < l; i += 1) {
|
||||
if (isDuplicate) {
|
||||
server.reply(updateAnnouncement, newPeerList[i]);
|
||||
} else {
|
||||
server.reply(joinAnnouncement, newPeerList[i]);
|
||||
}
|
||||
|
||||
nicks.push(newPeerList[i].nick); /* @legacy */
|
||||
users.push({
|
||||
...{
|
||||
channel,
|
||||
isme: false,
|
||||
},
|
||||
...getUserDetails(newPeerList[i]),
|
||||
});
|
||||
}
|
||||
|
||||
nicks.push(userInfo.nick); /* @legacy */
|
||||
users.push({ ...{ isme: true, isBot: socket.isBot }, ...userInfo });
|
||||
|
||||
// reply with channel peer list
|
||||
server.reply({
|
||||
cmd: 'onlineSet',
|
||||
nicks, /* @legacy */
|
||||
users,
|
||||
channel, // @todo Multichannel (?)
|
||||
}, socket);
|
||||
|
||||
socket.channel = channel; /* @legacy */
|
||||
socket.channels.push(channel);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} join/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'join',
|
||||
category: 'core',
|
||||
description: 'Join the target channel using the supplied nick and password',
|
||||
usage: `
|
||||
API: { cmd: 'join', nick: '<your nickname>', pass: '<optional password>', channel: '<target channel>' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
215
documentation/core_morestats.js.html
Normal file
215
documentation/core_morestats.js.html
Normal file
|
@ -0,0 +1,215 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: core/morestats.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: core/morestats.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Get stats
|
||||
* @version 1.0.0
|
||||
* @description Sends back current server stats to the calling client
|
||||
* @module morestats
|
||||
*/
|
||||
|
||||
/**
|
||||
* Format input time into string
|
||||
* @param {Date} time - Subject date
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
const formatTime = (time) => {
|
||||
let seconds = time[0] + time[1] / 1e9;
|
||||
|
||||
let minutes = Math.floor(seconds / 60);
|
||||
seconds %= 60;
|
||||
|
||||
let hours = Math.floor(minutes / 60);
|
||||
minutes %= 60;
|
||||
|
||||
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`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ core, server, socket }) {
|
||||
// gather connection and channel count
|
||||
const ips = {};
|
||||
const channels = {};
|
||||
// @todo use public channel flag
|
||||
const publicChanCounts = {
|
||||
lounge: 0,
|
||||
meta: 0,
|
||||
math: 0,
|
||||
physics: 0,
|
||||
chemistry: 0,
|
||||
technology: 0,
|
||||
programming: 0,
|
||||
games: 0,
|
||||
banana: 0,
|
||||
chinese: 0,
|
||||
};
|
||||
|
||||
// @todo code resuage between here and `session`; should share exported function
|
||||
server.clients.forEach((client) => {
|
||||
if (client.channel) {
|
||||
channels[client.channel] = true;
|
||||
ips[client.address] = true;
|
||||
if (typeof publicChanCounts[client.channel] !== 'undefined') {
|
||||
publicChanCounts[client.channel] += 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const uniqueClientCount = Object.keys(ips).length;
|
||||
const uniqueChannels = Object.keys(channels).length;
|
||||
const joins = core.stats.get('users-joined') || 0;
|
||||
const invites = core.stats.get('invites-sent') || 0;
|
||||
const messages = core.stats.get('messages-sent') || 0;
|
||||
const banned = core.stats.get('users-banned') || 0;
|
||||
const kicked = core.stats.get('users-kicked') || 0;
|
||||
const stats = core.stats.get('stats-requested') || 0;
|
||||
const uptime = formatTime(process.hrtime(core.stats.get('start-time')));
|
||||
|
||||
// dispatch info
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
users: uniqueClientCount,
|
||||
chans: uniqueChannels,
|
||||
joins,
|
||||
invites,
|
||||
messages,
|
||||
banned,
|
||||
kicked,
|
||||
stats,
|
||||
uptime,
|
||||
public: publicChanCounts,
|
||||
text: `current-connections: ${uniqueClientCount}
|
||||
current-channels: ${uniqueChannels}
|
||||
users-joined: ${joins}
|
||||
invites-sent: ${invites}
|
||||
messages-sent: ${messages}
|
||||
users-banned: ${banned}
|
||||
users-kicked: ${kicked}
|
||||
stats-requested: ${stats}
|
||||
server-uptime: ${uptime}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('stats-requested');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.statsCheck.bind(this), 26);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hooks chat commands checking for /stats
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function statsCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/stats')) {
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'morestats',
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} morestats/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'morestats',
|
||||
category: 'core',
|
||||
description: 'Sends back current server stats to the calling client',
|
||||
usage: `
|
||||
API: { cmd: 'morestats' }
|
||||
Text: /stats`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
83
documentation/core_ping.js.html
Normal file
83
documentation/core_ping.js.html
Normal file
|
@ -0,0 +1,83 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: core/ping.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: core/ping.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/* eslint no-empty-function: 0 */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Legacy support module
|
||||
* @version 1.0.0
|
||||
* @description This module is only in place to supress error notices legacy clients may get
|
||||
* @module ping
|
||||
*/
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run() { }
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} ping/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'ping',
|
||||
category: 'core',
|
||||
description: 'This module is only in place to supress error notices legacy clients may get',
|
||||
usage: 'none',
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
250
documentation/core_session.js.html
Normal file
250
documentation/core_session.js.html
Normal file
|
@ -0,0 +1,250 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: core/session.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: core/session.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/* eslint import/no-cycle: [0, { ignoreExternal: true }] */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Create or restore session
|
||||
* @version 1.0.0
|
||||
* @description Restore previous state by session or create new session
|
||||
* @module session
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
verifyNickname,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
restoreJoin,
|
||||
} from './join.js';
|
||||
|
||||
const SessionLocation = './session.key';
|
||||
|
||||
/**
|
||||
* Get a new json web token for the provided socket
|
||||
* @param {*} socket
|
||||
* @param {*} core
|
||||
* @returns {object}
|
||||
*/
|
||||
export function getSession(socket, core) {
|
||||
return jsonwebtoken.sign({
|
||||
channel: socket.channel,
|
||||
channels: socket.channels,
|
||||
color: socket.color,
|
||||
isBot: socket.isBot,
|
||||
level: socket.level,
|
||||
nick: socket.nick,
|
||||
trip: socket.trip,
|
||||
userid: socket.userid,
|
||||
uType: socket.uType,
|
||||
muzzled: socket.muzzled || false,
|
||||
banned: socket.banned || false,
|
||||
}, core.sessionKey, {
|
||||
expiresIn: '7 days',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply to target socket with session failure notice
|
||||
* @param {*} server
|
||||
* @param {*} socket
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function notifyFailure(server, socket) {
|
||||
server.reply({
|
||||
cmd: 'error',
|
||||
id: Errors.Session.BAD_SESSION,
|
||||
text: 'Invalid session',
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.token === 'undefined') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
let session = false;
|
||||
try {
|
||||
session = jsonwebtoken.verify(payload.token, core.sessionKey);
|
||||
} catch (err) {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
// validate session
|
||||
if (typeof session.channel !== 'string') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (Array.isArray(session.channels) === false) {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.color !== 'string' && typeof session.color !== 'boolean') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.isBot !== 'boolean') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.level !== 'number') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (verifyNickname(session.nick) === false) {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.trip !== 'string') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.userid !== 'number') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.uType !== 'string') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.muzzled !== 'boolean') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
if (typeof session.banned !== 'boolean') {
|
||||
return notifyFailure(server, socket);
|
||||
}
|
||||
|
||||
// populate socket info with validated session
|
||||
socket.channels = [];
|
||||
socket.color = session.color;
|
||||
socket.isBot = session.isBot;
|
||||
socket.level = session.level;
|
||||
socket.nick = session.nick;
|
||||
socket.trip = session.trip;
|
||||
socket.userid = session.userid;
|
||||
socket.uType = session.uType;
|
||||
socket.muzzled = session.muzzled;
|
||||
socket.banned = session.banned;
|
||||
|
||||
// global mod perks
|
||||
if (isModerator(socket.level)) {
|
||||
socket.ratelimitImmune = true;
|
||||
}
|
||||
|
||||
socket.hash = server.getSocketHash(socket);
|
||||
socket.hcProtocol = 2;
|
||||
|
||||
// dispatch info
|
||||
server.reply({
|
||||
cmd: 'session',
|
||||
restored: true,
|
||||
token: getSession(socket, core),
|
||||
channels: socket.channels,
|
||||
}, socket);
|
||||
|
||||
for (let i = 0, j = session.channels.length; i < j; i += 1) {
|
||||
restoreJoin({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
channel: session.channels[i],
|
||||
}, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready
|
||||
* @param {Object} core - Reference to core enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function init(core) {
|
||||
// load the encryption key if required
|
||||
if (typeof core.sessionKey === 'undefined') {
|
||||
core.sessionKey = fs.readFileSync(SessionLocation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} session/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'session',
|
||||
category: 'core',
|
||||
description: 'Restore previous state by session or create new session',
|
||||
usage: "API: { cmd: 'session', id: '<previous session>' }",
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
110
documentation/core_stats.js.html
Normal file
110
documentation/core_stats.js.html
Normal file
|
@ -0,0 +1,110 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: core/stats.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: core/stats.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Simple stats
|
||||
* @version 1.0.0
|
||||
* @description Sends back legacy server stats to the calling client
|
||||
* @module stats
|
||||
*/
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ core, server, socket }) {
|
||||
// gather connection and channel count
|
||||
let ips = {};
|
||||
let channels = {};
|
||||
// for (const client of server.clients) {
|
||||
server.clients.forEach((client) => {
|
||||
if (client.channel) {
|
||||
channels[client.channel] = true;
|
||||
ips[client.address] = true;
|
||||
}
|
||||
});
|
||||
|
||||
const uniqueClientCount = Object.keys(ips).length;
|
||||
const uniqueChannels = Object.keys(channels).length;
|
||||
|
||||
ips = null;
|
||||
channels = null;
|
||||
|
||||
// dispatch info
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `${uniqueClientCount} unique IPs in ${uniqueChannels} channels`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('stats-requested');
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} stats/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'stats',
|
||||
category: 'core',
|
||||
description: 'Sends back legacy server stats to the calling client',
|
||||
usage: `
|
||||
API: { cmd: 'stats' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
178
documentation/core_updateMessage.js.html
Normal file
178
documentation/core_updateMessage.js.html
Normal file
|
@ -0,0 +1,178 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: core/updateMessage.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: core/updateMessage.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author MinusGix ( https://github.com/MinusGix )
|
||||
* @summary Change target message
|
||||
* @version v1.0.0
|
||||
* @description Will alter a previously sent message using that message's customId
|
||||
* @module updateMessage
|
||||
*/
|
||||
|
||||
import {
|
||||
parseText,
|
||||
} from '../utility/_Text.js';
|
||||
import {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
ACTIVE_MESSAGES,
|
||||
MAX_MESSAGE_ID_LENGTH,
|
||||
} from './chat.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
server, socket, payload,
|
||||
}) {
|
||||
// undefined | "overwrite" | "append" | "prepend" | "complete"
|
||||
const { customId } = payload;
|
||||
let { mode, text } = payload;
|
||||
|
||||
if (!mode) {
|
||||
mode = 'overwrite';
|
||||
}
|
||||
|
||||
if (mode !== 'overwrite' && mode !== 'append' && mode !== 'prepend' && mode !== 'complete') {
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
if (!customId || typeof customId !== 'string' || customId.length > MAX_MESSAGE_ID_LENGTH) {
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
if (typeof (text) !== 'string') {
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
if (mode === 'overwrite') {
|
||||
text = parseText(text);
|
||||
|
||||
if (text === '') {
|
||||
text = '\u0000';
|
||||
}
|
||||
}
|
||||
|
||||
if (!text) {
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
// TODO: What score should we use for this? It isn't as space filling as chat messages.
|
||||
// But we also don't want a massive growing message.
|
||||
// Or flashing between huge and small. Etc.
|
||||
|
||||
let message;
|
||||
for (let i = 0; i < ACTIVE_MESSAGES.length; i += 1) {
|
||||
const msg = ACTIVE_MESSAGES[i];
|
||||
|
||||
if (msg.userid === socket.userid && msg.customId === customId) {
|
||||
message = ACTIVE_MESSAGES[i];
|
||||
if (mode === 'complete') {
|
||||
ACTIVE_MESSAGES[i].toDelete = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
return server.police.frisk(socket, 6);
|
||||
}
|
||||
|
||||
const outgoingPayload = {
|
||||
cmd: 'updateMessage',
|
||||
userid: socket.userid,
|
||||
channel: socket.channel,
|
||||
level: socket.level,
|
||||
mode,
|
||||
text,
|
||||
customId: message.customId,
|
||||
};
|
||||
|
||||
if (isAdmin(socket.level)) {
|
||||
outgoingPayload.admin = true;
|
||||
} else if (isModerator(socket.level)) {
|
||||
outgoingPayload.mod = true;
|
||||
}
|
||||
|
||||
server.broadcast(outgoingPayload, { channel: socket.channel });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "text", "customId"
|
||||
* @public
|
||||
* @typedef {Array} addmod/requiredData
|
||||
*/
|
||||
export const requiredData = ['text', 'customId'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} updateMessage/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'updateMessage',
|
||||
category: 'core',
|
||||
description: 'Update a message you have sent.',
|
||||
usage: `
|
||||
API: { cmd: 'updateMessage', mode: 'overwrite'|'append'|'prepend'|'complete', text: '<text to apply>', customId: '<customId sent with the chat message>' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
282
documentation/core_whisper.js.html
Normal file
282
documentation/core_whisper.js.html
Normal file
|
@ -0,0 +1,282 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: core/whisper.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: core/whisper.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Send whisper
|
||||
* @version 1.0.0
|
||||
* @description Display text on target users screen that only they can see
|
||||
* @module whisper
|
||||
* @todo This should be changed to it's own event type, instead of `info`
|
||||
and accept a `userid` rather than `nick`
|
||||
*/
|
||||
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
legacyWhisperOut,
|
||||
legacyWhisperReply,
|
||||
} from '../utility/_LegacyFunctions.js';
|
||||
|
||||
/**
|
||||
* Check and trim string provided by remote client
|
||||
* @param {string} text - Subject string
|
||||
* @private
|
||||
* @todo Move into utility module
|
||||
* @return {string|boolean}
|
||||
*/
|
||||
const parseText = (text) => {
|
||||
// verifies user input is text
|
||||
if (typeof text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let sanitizedText = 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ server, socket, payload }) {
|
||||
// if this is a legacy client add missing params to payload
|
||||
if (socket.hcProtocol === 1) {
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
// verify user input
|
||||
const text = parseText(payload.text);
|
||||
|
||||
if (!text) {
|
||||
// lets not send objects or empty text, yea?
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
// check for spam
|
||||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket, score)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
const outgoingPayload = {
|
||||
cmd: 'whisper',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
from: socket.userid,
|
||||
to: targetUser.userid,
|
||||
text,
|
||||
};
|
||||
|
||||
// send invite notice to target client
|
||||
if (targetUser.hcProtocol === 1) {
|
||||
server.reply(legacyWhisperOut(outgoingPayload, socket), targetUser);
|
||||
} else {
|
||||
server.reply(outgoingPayload, targetUser);
|
||||
}
|
||||
|
||||
// send invite notice to this client
|
||||
if (socket.hcProtocol === 1) {
|
||||
server.reply(legacyWhisperReply(outgoingPayload, targetUser.nick), socket);
|
||||
} else {
|
||||
server.reply(outgoingPayload, socket);
|
||||
}
|
||||
|
||||
targetUser.whisperReply = socket.nick;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.whisperCheck.bind(this), 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hooks chat commands checking for /whisper
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function whisperCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/whisper ') || payload.text.startsWith('/w ')) {
|
||||
const input = payload.text.split(' ');
|
||||
|
||||
// If there is no nickname target parameter
|
||||
if (!input[1]) {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Refer to `/help whisper` for instructions on how to use this command.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = input[1].replace(/@/g, '');
|
||||
input.splice(0, 2);
|
||||
const whisperText = input.join(' ');
|
||||
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'whisper',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
nick: target,
|
||||
text: whisperText,
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/reply ') || payload.text.startsWith('/r ')) {
|
||||
if (typeof socket.whisperReply === 'undefined') {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'Cannot reply to nobody',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const input = payload.text.split(' ');
|
||||
input.splice(0, 1);
|
||||
const whisperText = input.join(' ');
|
||||
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'whisper',
|
||||
nick: socket.whisperReply,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
text: whisperText,
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "nick", "text"
|
||||
* @public
|
||||
* @typedef {Array} whisper/requiredData
|
||||
*/
|
||||
export const requiredData = ['nick', 'text'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} whisper/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'whisper',
|
||||
category: 'core',
|
||||
description: 'Display text on target users screen that only they can see',
|
||||
usage: `
|
||||
API: { cmd: 'whisper', nick: '<target name>', text: '<text to whisper>' }
|
||||
Text: /whisper <target name> <text to whisper>
|
||||
Text: /w <target name> <text to whisper>
|
||||
Alt Text: /reply <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>`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
BIN
documentation/fonts/OpenSans-Bold-webfont.eot
Normal file
BIN
documentation/fonts/OpenSans-Bold-webfont.eot
Normal file
Binary file not shown.
1830
documentation/fonts/OpenSans-Bold-webfont.svg
Normal file
1830
documentation/fonts/OpenSans-Bold-webfont.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 116 KiB |
BIN
documentation/fonts/OpenSans-Bold-webfont.woff
Normal file
BIN
documentation/fonts/OpenSans-Bold-webfont.woff
Normal file
Binary file not shown.
BIN
documentation/fonts/OpenSans-BoldItalic-webfont.eot
Normal file
BIN
documentation/fonts/OpenSans-BoldItalic-webfont.eot
Normal file
Binary file not shown.
1830
documentation/fonts/OpenSans-BoldItalic-webfont.svg
Normal file
1830
documentation/fonts/OpenSans-BoldItalic-webfont.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 118 KiB |
BIN
documentation/fonts/OpenSans-BoldItalic-webfont.woff
Normal file
BIN
documentation/fonts/OpenSans-BoldItalic-webfont.woff
Normal file
Binary file not shown.
BIN
documentation/fonts/OpenSans-Italic-webfont.eot
Normal file
BIN
documentation/fonts/OpenSans-Italic-webfont.eot
Normal file
Binary file not shown.
1830
documentation/fonts/OpenSans-Italic-webfont.svg
Normal file
1830
documentation/fonts/OpenSans-Italic-webfont.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 120 KiB |
BIN
documentation/fonts/OpenSans-Italic-webfont.woff
Normal file
BIN
documentation/fonts/OpenSans-Italic-webfont.woff
Normal file
Binary file not shown.
BIN
documentation/fonts/OpenSans-Light-webfont.eot
Normal file
BIN
documentation/fonts/OpenSans-Light-webfont.eot
Normal file
Binary file not shown.
1831
documentation/fonts/OpenSans-Light-webfont.svg
Normal file
1831
documentation/fonts/OpenSans-Light-webfont.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 114 KiB |
BIN
documentation/fonts/OpenSans-Light-webfont.woff
Normal file
BIN
documentation/fonts/OpenSans-Light-webfont.woff
Normal file
Binary file not shown.
BIN
documentation/fonts/OpenSans-LightItalic-webfont.eot
Normal file
BIN
documentation/fonts/OpenSans-LightItalic-webfont.eot
Normal file
Binary file not shown.
1835
documentation/fonts/OpenSans-LightItalic-webfont.svg
Normal file
1835
documentation/fonts/OpenSans-LightItalic-webfont.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 120 KiB |
BIN
documentation/fonts/OpenSans-LightItalic-webfont.woff
Normal file
BIN
documentation/fonts/OpenSans-LightItalic-webfont.woff
Normal file
Binary file not shown.
BIN
documentation/fonts/OpenSans-Regular-webfont.eot
Normal file
BIN
documentation/fonts/OpenSans-Regular-webfont.eot
Normal file
Binary file not shown.
1831
documentation/fonts/OpenSans-Regular-webfont.svg
Normal file
1831
documentation/fonts/OpenSans-Regular-webfont.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 117 KiB |
BIN
documentation/fonts/OpenSans-Regular-webfont.woff
Normal file
BIN
documentation/fonts/OpenSans-Regular-webfont.woff
Normal file
Binary file not shown.
105
documentation/index.html
Normal file
105
documentation/index.html
Normal file
|
@ -0,0 +1,105 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Home</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Home</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h3> </h3>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article><h1>hack.chat</h1>
|
||||
<p><a href="https://hack.chat/">hack.chat</a> is a minimal, distraction-free, accountless, logless, disappearing chat service which is easily deployable as your own service. The current client comes bundled with LaTeX rendering provided by <a href="https://github.com/Khan/KaTeX">KaTeX</a> and code syntax highlighting provided by <a href="https://github.com/isagalaev/highlight.js">highlight.js</a>.</p>
|
||||
<p>A list of software developed for the hack.chat framework can be found at the <a href="https://github.com/hack-chat/3rd-party-software-list">3rd party software list</a> repository. This includes bots, clients, docker containers, etc.</p>
|
||||
<p>This is a backwards compatible continuation of the <a href="https://github.com/AndrewBelt/hack.chat">work by Andrew Belt</a>. The server code has been updated to ES6 along with several new features including new commands and hot-reload of the commands/protocol. There is also <a href="documentation/index.html">documentation</a>.</p>
|
||||
<h1>Installation</h1>
|
||||
<h2>Prerequisites</h2>
|
||||
<ul>
|
||||
<li><a href="https://nodejs.org/">node.js v16.14.0</a> or higher</li>
|
||||
<li><a href="https://nodejs.org/">npm 8.5.4</a> or higher</li>
|
||||
</ul>
|
||||
<h2>Developer Installation</h2>
|
||||
<ol>
|
||||
<li><a href="https://help.github.com/articles/cloning-a-repository/">Clone</a> the repository: <code>git clone https://github.com/hack-chat/main.git</code></li>
|
||||
<li>Change the directory: <code>cd main</code></li>
|
||||
<li>Install the dependencies: <code>npm install</code></li>
|
||||
<li>Launch: <code>npm start</code></li>
|
||||
</ol>
|
||||
<h2>Live Deployment Installation</h2>
|
||||
<p>See <a href="documentation/DEPLOY.md">DEPLOY.md</a></p>
|
||||
<h1>Contributing</h1>
|
||||
<ul>
|
||||
<li>Use two space indents.</li>
|
||||
<li>Name files in camelCase.</li>
|
||||
</ul>
|
||||
<h1>Credits</h1>
|
||||
<ul>
|
||||
<li><a href="https://github.com/marzavec"><strong>Marzavec</strong></a> - <em>Initial work</em></li>
|
||||
<li><a href="https://github.com/MinusGix"><strong>MinusGix</strong></a> - <em>Base updates</em></li>
|
||||
<li><a href="https://github.com/neelkamath"><strong>Neel Kamath</strong></a> - <em>Base Documentation</em></li>
|
||||
<li><a href="https://github.com/po5i"><strong>Carlos Villavicencio</strong></a> - <em>Syntax Highlighting Integration</em></li>
|
||||
<li><a href="https://github.com/OpSimple"><strong>OpSimple</strong></a> - <em>Modules Added: dumb.js & speak.js</em></li>
|
||||
<li><a href="https://github.com/AndrewBelt"><strong>Andrew Belt</strong></a>, for original base work</li>
|
||||
<li><a href="https://github.com/wwandrew"><strong>wwandrew</strong></a>, for finding server flaws (including attack vectors) and submitting <s><em><strong>incredibly detailed</strong></em></s> bug reports</li>
|
||||
<li><a href="https://github.com/hack-chat/main/graphs/contributors"><strong>Everyone else</strong></a> who participated in this project.</li>
|
||||
</ul>
|
||||
<h1>License</h1>
|
||||
<p>This project is licensed under the <a href="LICENSE">MIT License</a>.</p></article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
117
documentation/internal_disconnect.js.html
Normal file
117
documentation/internal_disconnect.js.html
Normal file
|
@ -0,0 +1,117 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: internal/disconnect.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: internal/disconnect.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Disconnection handler
|
||||
* @version 1.0.0
|
||||
* @description The server invokes this module each time a websocket connection is disconnected
|
||||
* @module disconnect
|
||||
*/
|
||||
|
||||
import {
|
||||
socketInChannel,
|
||||
} from '../utility/_Channels.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ server, socket, payload }) {
|
||||
if (payload.cmdKey !== server.cmdKey) {
|
||||
// internal command attempt by client, increase rate limit chance and ignore
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// send leave notice to client peers
|
||||
// @todo Multichannel update
|
||||
if (socket.channel) {
|
||||
const isDuplicate = socketInChannel(server, socket.channel, socket);
|
||||
|
||||
if (isDuplicate === false) {
|
||||
server.broadcast({
|
||||
cmd: 'onlineRemove',
|
||||
nick: socket.nick,
|
||||
}, { channel: socket.channel });
|
||||
}
|
||||
}
|
||||
|
||||
// commit close just in case
|
||||
socket.terminate();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "cmdKey"
|
||||
* @public
|
||||
* @typedef {Array} disconnect/requiredData
|
||||
*/
|
||||
export const requiredData = ['cmdKey'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} disconnect/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'disconnect',
|
||||
category: 'internal',
|
||||
description: 'Internally used to relay disconnect events to clients',
|
||||
usage: 'Internal Use Only',
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
102
documentation/internal_socketreply.js.html
Normal file
102
documentation/internal_socketreply.js.html
Normal file
|
@ -0,0 +1,102 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: internal/socketreply.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: internal/socketreply.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Bridge warning events to a user
|
||||
* @version 1.0.0
|
||||
* @description If a warning occurs within the server, this module will relay the warning to the
|
||||
* client
|
||||
* @module socketreply
|
||||
*/
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({ server, socket, payload }) {
|
||||
if (payload.cmdKey !== server.cmdKey) {
|
||||
// internal command attempt by client, increase rate limit chance and ignore
|
||||
return server.police.frisk(socket, 20);
|
||||
}
|
||||
|
||||
// send warning to target socket
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: payload.text,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "cmdKey", "text"
|
||||
* @public
|
||||
* @typedef {Array} socketreply/requiredData
|
||||
*/
|
||||
export const requiredData = ['cmdKey', 'text'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} socketreply/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'socketreply',
|
||||
category: 'internal',
|
||||
description: 'Internally used to relay warnings to clients',
|
||||
usage: 'Internal Use Only',
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
166
documentation/mod_ban.js.html
Normal file
166
documentation/mod_ban.js.html
Normal file
|
@ -0,0 +1,166 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: mod/ban.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: mod/ban.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Ban a user
|
||||
* @version 1.0.0
|
||||
* @description Bans target user by name
|
||||
* @module ban
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
getUserDetails,
|
||||
levels,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof payload.nick !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
} else if (typeof payload.userid !== 'number') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// find target user
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
const targetNick = targetUser.nick;
|
||||
|
||||
// i guess banning mods or admins isn't the best idea?
|
||||
if (targetUser.level >= socket.level) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Cannot ban other users of the same level, how rude',
|
||||
id: Errors.Global.PERMISSION,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// commit arrest record
|
||||
server.police.arrest(targetUser.address, targetUser.hash);
|
||||
|
||||
console.log(`${socket.nick} [${socket.trip}] banned ${targetNick} in ${socket.channel}`);
|
||||
|
||||
// notify normal users
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Banned ${targetNick}`,
|
||||
user: getUserDetails(targetUser),
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, { channel: socket.channel, level: (level) => level < levels.moderator });
|
||||
|
||||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick}#${socket.trip} banned ${targetNick} in ${payload.channel}, userhash: ${targetUser.hash}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
inChannel: payload.channel,
|
||||
user: getUserDetails(targetUser),
|
||||
banner: getUserDetails(socket),
|
||||
}, { level: isModerator });
|
||||
|
||||
// force connection closed
|
||||
targetUser.terminate();
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('users-banned');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} ban/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'ban',
|
||||
category: 'moderators',
|
||||
description: 'Bans target user by name',
|
||||
usage: `
|
||||
API: { cmd: 'ban', nick: '<target nickname>' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
137
documentation/mod_disablecaptcha.js.html
Normal file
137
documentation/mod_disablecaptcha.js.html
Normal file
|
@ -0,0 +1,137 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: mod/disablecaptcha.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: mod/disablecaptcha.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Disables the captcha
|
||||
* @version 1.0.0
|
||||
* @description Disables the captcha on the channel specified in the channel property,
|
||||
* default is current channel
|
||||
* @module disablecaptcha
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready
|
||||
* @param {Object} core - Reference to core enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function init(core) {
|
||||
if (typeof core.captchas === 'undefined') {
|
||||
core.captchas = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
let targetChannel;
|
||||
|
||||
if (typeof payload.channel !== 'string') {
|
||||
if (typeof socket.channel !== 'string') { // @todo Multichannel
|
||||
return false; // silently fail
|
||||
}
|
||||
|
||||
targetChannel = socket.channel;
|
||||
} else {
|
||||
targetChannel = payload.channel;
|
||||
}
|
||||
|
||||
if (!core.captchas[targetChannel]) {
|
||||
return server.reply({
|
||||
cmd: 'info',
|
||||
text: 'Captcha is not enabled.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
core.captchas[targetChannel] = false;
|
||||
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Captcha disabled on: ${targetChannel}`,
|
||||
channel: false, // @todo Multichannel, false for global info
|
||||
}, { channel: targetChannel, level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} disablecaptcha/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'disablecaptcha',
|
||||
category: 'moderators',
|
||||
description: 'Disables the captcha on the channel specified in the channel property, default is current channel',
|
||||
usage: `
|
||||
API: { cmd: 'disablecaptcha', channel: '<optional channel, defaults to your current channel' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
425
documentation/mod_dumb.js.html
Normal file
425
documentation/mod_dumb.js.html
Normal file
|
@ -0,0 +1,425 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: mod/dumb.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: mod/dumb.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/* eslint no-param-reassign: 0 */
|
||||
/* eslint no-multi-assign: 0 */
|
||||
|
||||
/**
|
||||
* @author OpSimple ( https://github.com/OpSimple )
|
||||
* @summary Muzzle a user
|
||||
* @version 1.0.0
|
||||
* @description Globally shadow mute a connection. Optional allies array will see muted messages.
|
||||
* @module dumb
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
legacyInviteReply,
|
||||
legacyWhisperReply,
|
||||
} from '../utility/_LegacyFunctions.js';
|
||||
|
||||
/**
|
||||
* Returns the channel that should be invited to.
|
||||
* @param {any} channel
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
export function getChannel(channel = undefined) {
|
||||
if (typeof channel === 'string') {
|
||||
return channel;
|
||||
}
|
||||
return Math.random().toString(36).substr(2, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and trim string provided by remote client
|
||||
* @param {string} text - Subject string
|
||||
* @private
|
||||
* @todo Move into utility module
|
||||
* @return {string|boolean}
|
||||
*/
|
||||
const parseText = (text) => {
|
||||
// verifies user input is text
|
||||
if (typeof text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let sanitizedText = 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready
|
||||
* @param {Object} core - Reference to core enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function init(core) {
|
||||
if (typeof core.muzzledHashes === 'undefined') {
|
||||
core.muzzledHashes = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
payload.channel = socket.channel;
|
||||
} else if (typeof payload.userid !== 'number') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// find target user
|
||||
const targetUser = findUser(server, payload);
|
||||
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// likely dont need this, muting mods and admins is fine
|
||||
if (targetUser.level >= socket.level) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'This trick wont work on users of the same level',
|
||||
id: Errors.Global.PERMISSION,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// store hash in mute list
|
||||
const record = core.muzzledHashes[targetUser.hash] = {
|
||||
dumb: true,
|
||||
};
|
||||
|
||||
// store allies if needed
|
||||
if (payload.allies && Array.isArray(payload.allies)) {
|
||||
record.allies = payload.allies;
|
||||
}
|
||||
|
||||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick}#${socket.trip} muzzled ${targetUser.nick} in ${payload.channel}, userhash: ${targetUser.hash}`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.chatCheck.bind(this), 10);
|
||||
server.registerHook('in', 'invite', this.inviteCheck.bind(this), 10);
|
||||
server.registerHook('in', 'whisper', this.whisperCheck.bind(this), 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hook incoming chat commands, shadow-prevent chat if they are muzzled
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function chatCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (core.muzzledHashes[socket.hash]) {
|
||||
// build fake chat payload
|
||||
const outgoingPayload = {
|
||||
cmd: 'chat',
|
||||
nick: socket.nick, /* @legacy */
|
||||
uType: socket.uType, /* @legacy */
|
||||
userid: socket.userid,
|
||||
channel: socket.channel,
|
||||
text: payload.text,
|
||||
level: socket.level,
|
||||
};
|
||||
|
||||
if (socket.trip) {
|
||||
outgoingPayload.trip = socket.trip;
|
||||
}
|
||||
|
||||
if (socket.color) {
|
||||
outgoingPayload.color = socket.color;
|
||||
}
|
||||
|
||||
// broadcast to any duplicate connections in channel
|
||||
server.broadcast(outgoingPayload, { channel: socket.channel, hash: socket.hash });
|
||||
|
||||
// broadcast to allies, if any
|
||||
if (core.muzzledHashes[socket.hash].allies) {
|
||||
server.broadcast(
|
||||
outgoingPayload,
|
||||
{
|
||||
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, 9);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* shadow-prevent all invites from muzzled users
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function inviteCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (core.muzzledHashes[socket.hash]) {
|
||||
// check for spam
|
||||
if (server.police.frisk(socket, 2)) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You are sending invites too fast. Wait a moment before trying again.',
|
||||
id: Errors.Invite.RATELIMIT,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// verify user input
|
||||
// if this is a legacy client add missing params to payload
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof socket.channel === 'undefined' || typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
} else if (typeof payload.userid !== 'number' || typeof payload.channel !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// @todo Verify this socket is part of payload.channel - multichannel patch
|
||||
// find target user
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// generate common channel
|
||||
const channel = getChannel(payload.to);
|
||||
|
||||
// build invite
|
||||
const outgoingPayload = {
|
||||
cmd: 'invite',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
from: socket.userid,
|
||||
to: targetUser.userid,
|
||||
inviteChannel: channel,
|
||||
};
|
||||
|
||||
// send invite notice to this client
|
||||
if (socket.hcProtocol === 1) {
|
||||
server.reply(legacyInviteReply(outgoingPayload, targetUser.nick), socket);
|
||||
} else {
|
||||
server.reply(outgoingPayload, socket);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* shadow-prevent all whispers from muzzled users
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function whisperCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (core.muzzledHashes[socket.hash]) {
|
||||
// if this is a legacy client add missing params to payload
|
||||
if (socket.hcProtocol === 1) {
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
// verify user input
|
||||
const text = parseText(payload.text);
|
||||
|
||||
if (!text) {
|
||||
// lets not send objects or empty text, yea?
|
||||
return server.police.frisk(socket, 13);
|
||||
}
|
||||
|
||||
// check for spam
|
||||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket, score)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
const outgoingPayload = {
|
||||
cmd: 'whisper',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
from: socket.userid,
|
||||
to: targetUser.userid,
|
||||
text,
|
||||
};
|
||||
|
||||
// send invite notice to this client
|
||||
if (socket.hcProtocol === 1) {
|
||||
server.reply(legacyWhisperReply(outgoingPayload, targetUser.nick), socket);
|
||||
} else {
|
||||
server.reply(outgoingPayload, socket);
|
||||
}
|
||||
|
||||
targetUser.whisperReply = socket.nick;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} dumb/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {Array} aliases - An array of alternative cmd names
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'dumb',
|
||||
category: 'moderators',
|
||||
description: 'Globally shadow mute a connection. Optional allies array will see muted messages.',
|
||||
aliases: ['muzzle', 'mute'],
|
||||
usage: `
|
||||
API: { cmd: 'dumb', nick: '<target nick>', allies: ['<optional nick array>', ...] }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
330
documentation/mod_enablecaptcha.js.html
Normal file
330
documentation/mod_enablecaptcha.js.html
Normal file
|
@ -0,0 +1,330 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: mod/enablecaptcha.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: mod/enablecaptcha.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/* eslint no-param-reassign: 0 */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Enables the captcha
|
||||
* @version 1.0.0
|
||||
* @description Enables the captcha on the channel specified in the channel property,
|
||||
* default is current channel
|
||||
* @module enablecaptcha
|
||||
*/
|
||||
|
||||
import captcha from 'ascii-captcha';
|
||||
|
||||
import {
|
||||
isTrustedUser,
|
||||
isModerator,
|
||||
verifyNickname,
|
||||
getUserPerms,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
canJoinChannel,
|
||||
} from '../utility/_Channels.js';
|
||||
import {
|
||||
upgradeLegacyJoin,
|
||||
legacyLevelToLabel,
|
||||
} from '../utility/_LegacyFunctions.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready
|
||||
* @param {Object} core - Reference to core enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function init(core) {
|
||||
if (typeof core.captchas === 'undefined') {
|
||||
core.captchas = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
let targetChannel;
|
||||
|
||||
if (typeof payload.channel !== 'string') {
|
||||
if (typeof socket.channel !== 'string') { // @todo Multichannel
|
||||
return false; // silently fail
|
||||
}
|
||||
|
||||
targetChannel = socket.channel;
|
||||
} else {
|
||||
targetChannel = payload.channel;
|
||||
}
|
||||
|
||||
if (core.captchas[targetChannel]) {
|
||||
return server.reply({
|
||||
cmd: 'info',
|
||||
text: 'Captcha is already enabled.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
core.captchas[targetChannel] = true;
|
||||
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Captcha enabled on: ${targetChannel}`,
|
||||
channel: socket.channel, // @todo Multichannel, false for global info
|
||||
}, { channel: socket.channel, level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.chatCheck.bind(this), 5);
|
||||
server.registerHook('in', 'join', this.joinCheck.bind(this), 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hook incoming chat commands, check if they are answering a captcha
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function chatCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// always verifiy user input
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof socket.captcha !== 'undefined') {
|
||||
if (socket.captcha.awaiting === true) {
|
||||
if (payload.text === socket.captcha.solution) {
|
||||
if (typeof socket.captcha.whitelist === 'undefined') {
|
||||
socket.captcha.whitelist = [];
|
||||
}
|
||||
|
||||
socket.captcha.whitelist.push(socket.captcha.origChannel);
|
||||
socket.captcha.awaiting = false;
|
||||
|
||||
if (socket.hcProtocol === 1) {
|
||||
core.commands.handleCommand(server, socket, {
|
||||
cmd: 'join',
|
||||
nick: `${socket.captcha.origNick}#${socket.captcha.origPass}`,
|
||||
channel: socket.captcha.origChannel,
|
||||
});
|
||||
} else {
|
||||
core.commands.handleCommand(server, socket, {
|
||||
cmd: 'join',
|
||||
nick: socket.captcha.origNick,
|
||||
pass: socket.captcha.origPass,
|
||||
channel: socket.captcha.origChannel,
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
server.police.frisk(socket, 7);
|
||||
socket.terminate();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming join command is invoked;
|
||||
* hook incoming join commands, check if they are joining a captcha protected channel
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function joinCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// check if channel has captcha enabled
|
||||
if (core.captchas[payload.channel] !== true) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
// `join` is the legacy entry point, check if it needs to be upgraded
|
||||
const origPayload = { ...payload };
|
||||
if (typeof socket.hcProtocol === 'undefined') {
|
||||
payload = upgradeLegacyJoin(server, socket, payload);
|
||||
}
|
||||
|
||||
// store payload values
|
||||
const { channel, nick, pass } = payload;
|
||||
|
||||
// check if a client is able to join target channel
|
||||
const mayJoin = canJoinChannel(channel, socket);
|
||||
if (mayJoin !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You may not join that channel.',
|
||||
id: mayJoin,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// calling socket already in a channel
|
||||
// @todo multichannel update, will remove
|
||||
if (typeof socket.channel !== 'undefined') {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove this
|
||||
text: 'Joining more than one channel is not currently supported',
|
||||
id: Errors.Join.ALREADY_JOINED,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
// end todo
|
||||
|
||||
// validates the user input for `nick`
|
||||
if (verifyNickname(nick, socket) !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Nickname must consist of up to 24 letters, numbers, and underscores',
|
||||
id: Errors.Join.INVALID_NICK,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// get trip and level
|
||||
const { trip, level } = getUserPerms(pass, core.saltKey, core.appConfig.data, channel);
|
||||
|
||||
// store the user values
|
||||
const userInfo = {
|
||||
nick,
|
||||
trip,
|
||||
uType: legacyLevelToLabel(level),
|
||||
hash: socket.hash,
|
||||
level,
|
||||
userid: socket.userid,
|
||||
isBot: socket.isBot,
|
||||
color: socket.color,
|
||||
channel,
|
||||
};
|
||||
|
||||
if (userInfo.uType === 'user') {
|
||||
if (userInfo.trip == null || isTrustedUser(level) === false) {
|
||||
if (typeof socket.captcha === 'undefined') {
|
||||
socket.captcha = {
|
||||
awaiting: true,
|
||||
origChannel: payload.channel,
|
||||
origNick: payload.nick,
|
||||
origPass: pass,
|
||||
solution: captcha.generateRandomText(6),
|
||||
};
|
||||
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Enter the following to join (case-sensitive):',
|
||||
channel: payload.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
server.reply({
|
||||
cmd: 'captcha',
|
||||
text: captcha.word2Transformedstr(socket.captcha.solution),
|
||||
channel: payload.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
socket.terminate();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return origPayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} enablecaptcha/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'enablecaptcha',
|
||||
category: 'moderators',
|
||||
description: 'Enables a captcha in the current channel you are in',
|
||||
usage: `
|
||||
API: { cmd: 'enablecaptcha', channel: '<optional channel, defaults to your current channel>' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
250
documentation/mod_forcecolor.js.html
Normal file
250
documentation/mod_forcecolor.js.html
Normal file
|
@ -0,0 +1,250 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: mod/forcecolor.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: mod/forcecolor.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Color a user
|
||||
* @version 1.0.0
|
||||
* @description Forces a user nick to become a certain color
|
||||
* @module forcecolor
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels.js';
|
||||
|
||||
/**
|
||||
* Validate a string as a valid hex color string
|
||||
* @param {string} color - Color string to validate
|
||||
* @private
|
||||
* @todo Move into utility module
|
||||
* @return {boolean}
|
||||
*/
|
||||
const verifyColor = (color) => /(^[0-9A-F]{6}$)|(^[0-9A-F]{3}$)/i.test(color);
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
const { channel } = socket;
|
||||
if (typeof payload.channel === 'undefined') {
|
||||
payload.channel = channel;
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof payload.color !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// make sure requested nickname meets standards
|
||||
const newColor = payload.color.trim().toUpperCase().replace(/#/g, '');
|
||||
if (newColor !== 'RESET' && !verifyColor(newColor)) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Invalid color! Color must be in hex value',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// find target user
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// TODO: Change this uType to use level / uac
|
||||
// i guess coloring mods or admins isn't the best idea?
|
||||
if (targetUser.uType !== 'user') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (newColor === 'RESET') {
|
||||
targetUser.color = false;
|
||||
} else {
|
||||
targetUser.color = newColor;
|
||||
}
|
||||
|
||||
// build update notice with new color
|
||||
const updateNotice = {
|
||||
...getUserDetails(targetUser),
|
||||
...{
|
||||
cmd: 'updateUser',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
},
|
||||
};
|
||||
|
||||
// notify channel that the user has changed their name
|
||||
// @todo this should be sent to every channel the user is in (multichannel)
|
||||
server.broadcast(updateNotice, { channel: socket.channel });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.colorCheck.bind(this), 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hooks chat commands checking for /forcecolor
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function colorCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/forcecolor ')) {
|
||||
const input = payload.text.split(' ');
|
||||
|
||||
// If there is no nickname target parameter
|
||||
if (input[1] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Refer to `/help forcecolor` for instructions on how to use this command.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (input[2] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Refer to `/help forcecolor` for instructions on how to use this command.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = input[1].replace(/@/g, '');
|
||||
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'forcecolor',
|
||||
nick: target,
|
||||
color: input[2],
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following payload properties are required to invoke this module:
|
||||
* "nick", "color"
|
||||
* @public
|
||||
* @typedef {Array} forcecolor/requiredData
|
||||
*/
|
||||
export const requiredData = ['nick', 'color'];
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} forcecolor/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'forcecolor',
|
||||
category: 'moderators',
|
||||
description: 'Forces a user nick to become a certain color',
|
||||
usage: `
|
||||
API: { cmd: 'forcecolor', nick: '<target nick>', color: '<color as hex>' }
|
||||
Text: /forcecolor <target nick> <color as hex>`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
203
documentation/mod_kick.js.html
Normal file
203
documentation/mod_kick.js.html
Normal file
|
@ -0,0 +1,203 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: mod/kick.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: mod/kick.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Give da boot
|
||||
* @version 1.0.0
|
||||
* @description Silently forces target client(s) into another channel
|
||||
* @module kick
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
levels,
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
findUsers,
|
||||
} from '../utility/_Channels.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof payload.nick !== 'string') {
|
||||
if (typeof payload.nick !== 'object' && !Array.isArray(payload.nick)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
} else if (typeof payload.userid !== 'number') {
|
||||
// @todo create multi-ban ui
|
||||
if (typeof payload.userid !== 'object' && !Array.isArray(payload.userid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// find target user(s)
|
||||
const badClients = findUsers(server, payload);
|
||||
if (badClients.length === 0) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user(s) in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// check if found targets are kickable, add them to the list if they are
|
||||
const kicked = [];
|
||||
for (let i = 0, j = badClients.length; i < j; i += 1) {
|
||||
if (badClients[i].level >= socket.level) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Cannot kick other users with the same level, how rude',
|
||||
id: Errors.Global.PERMISSION,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
} else {
|
||||
kicked.push(badClients[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (kicked.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let destChannel;
|
||||
if (typeof payload.to === 'string' && !!payload.to.trim()) {
|
||||
destChannel = payload.to;
|
||||
} else {
|
||||
destChannel = Math.random().toString(36).substr(2, 8);
|
||||
}
|
||||
|
||||
// Announce the kicked clients arrival in destChannel and that they were kicked
|
||||
// Before they arrive, so they don't see they got moved
|
||||
for (let i = 0; i < kicked.length; i += 1) {
|
||||
server.broadcast({
|
||||
...getUserDetails(kicked[i]),
|
||||
...{
|
||||
cmd: 'onlineAdd',
|
||||
channel: destChannel, // @todo Multichannel
|
||||
},
|
||||
}, { channel: destChannel });
|
||||
}
|
||||
|
||||
// Move all kicked clients to the new channel
|
||||
for (let i = 0; i < kicked.length; i += 1) {
|
||||
// @todo multi-channel update
|
||||
kicked[i].channel = destChannel;
|
||||
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${kicked[i].nick} was banished to ?${destChannel}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, { channel: socket.channel, level: isModerator });
|
||||
|
||||
console.log(`${socket.nick} [${socket.trip}] kicked ${kicked[i].nick} in ${socket.channel} to ${destChannel} `);
|
||||
}
|
||||
|
||||
// broadcast client leave event
|
||||
for (let i = 0, j = kicked.length; i < j; i += 1) {
|
||||
server.broadcast({
|
||||
cmd: 'onlineRemove',
|
||||
userid: kicked[i].userid,
|
||||
nick: kicked[i].nick,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, { channel: socket.channel });
|
||||
}
|
||||
|
||||
// publicly broadcast kick event
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Kicked ${kicked.map((k) => k.nick).join(', ')}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, { channel: socket.channel, level: (level) => level < levels.moderator });
|
||||
|
||||
// stats are fun
|
||||
core.stats.increment('users-kicked', kicked.length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} kick/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'kick',
|
||||
category: 'moderators',
|
||||
description: 'Silently forces target client(s) into another channel. `nick` may be string or array of strings',
|
||||
usage: `
|
||||
API: { cmd: 'kick', nick: '<target nick>', to: '<optional target channel>' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
382
documentation/mod_lockroom.js.html
Normal file
382
documentation/mod_lockroom.js.html
Normal file
|
@ -0,0 +1,382 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: mod/lockroom.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: mod/lockroom.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/* eslint no-param-reassign: 0 */
|
||||
|
||||
/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Locks the channel
|
||||
* @version 1.0.0
|
||||
* @description Locks a channel preventing default levels from joining
|
||||
* @module lockroom
|
||||
*/
|
||||
|
||||
import {
|
||||
isTrustedUser,
|
||||
isModerator,
|
||||
verifyNickname,
|
||||
getUserPerms,
|
||||
} from '../utility/_UAC.js';
|
||||
import {
|
||||
upgradeLegacyJoin,
|
||||
legacyLevelToLabel,
|
||||
} from '../utility/_LegacyFunctions.js';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants.js';
|
||||
import {
|
||||
canJoinChannel,
|
||||
} from '../utility/_Channels.js';
|
||||
|
||||
const danteQuotes = [
|
||||
'Do not be afraid; our fate cannot be taken from us; it is a gift.',
|
||||
'In the middle of the journey of our life I found myself within a dark woods where the straight way was lost.',
|
||||
'There is no greater sorrow then to recall our times of joy in wretchedness.',
|
||||
'They yearn for what they fear for.',
|
||||
'Through me you go into a city of weeping; through me you go into eternal pain; through me you go amongst the lost people',
|
||||
'From there we came outside and saw the stars',
|
||||
'But the stars that marked our starting fall away. We must go deeper into greater pain, for it is not permitted that we stay.',
|
||||
'Hope not ever to see Heaven. I have come to lead you to the other shore; into eternal darkness; into fire and into ice.',
|
||||
'As little flowers, which the chill of night has bent and huddled, when the white sun strikes, grow straight and open fully on their stems, so did I, too, with my exhausted force.',
|
||||
'At grief so deep the tongue must wag in vain; the language of our sense and memory lacks the vocabulary of such pain.',
|
||||
'Thence we came forth to rebehold the stars.',
|
||||
'He is, most of all, l\'amor che move il sole e l\'altre stelle.',
|
||||
'The poets leave hell and again behold the stars.',
|
||||
'One ought to be afraid of nothing other then things possessed of power to do us harm, but things innoucuous need not be feared.',
|
||||
'As phantoms frighten beasts when shadows fall.',
|
||||
'We were men once, though we\'ve become trees',
|
||||
'Here pity only lives when it is dead',
|
||||
'Lasciate ogne speranza, voi ch\'intrate.',
|
||||
'There is no greater sorrow than thinking back upon a happy time in misery',
|
||||
'My thoughts were full of other things When I wandered off the path.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready
|
||||
* @param {Object} core - Reference to core enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function init(core) {
|
||||
if (typeof core.locked === 'undefined') {
|
||||
core.locked = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
let targetChannel;
|
||||
|
||||
if (typeof payload.channel !== 'string') {
|
||||
if (typeof socket.channel !== 'string') { // @todo Multichannel
|
||||
return false; // silently fail
|
||||
}
|
||||
|
||||
targetChannel = socket.channel;
|
||||
} else {
|
||||
targetChannel = payload.channel;
|
||||
}
|
||||
|
||||
if (core.locked[targetChannel]) {
|
||||
return server.reply({
|
||||
cmd: 'info',
|
||||
text: 'Channel is already locked.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// apply lock flag to channel list
|
||||
core.locked[targetChannel] = true;
|
||||
|
||||
// inform mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `Channel: ?${targetChannel} lock enabled by [${socket.trip}]${socket.nick}`,
|
||||
channel: false, // @todo Multichannel, false for global info
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready to register this modules hooks
|
||||
* @param {Object} server - Reference to server enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'changenick', this.changeNickCheck.bind(this), 1);
|
||||
server.registerHook('in', 'whisper', this.whisperCheck.bind(this), 1);
|
||||
server.registerHook('in', 'chat', this.chatCheck.bind(this), 1);
|
||||
server.registerHook('in', 'invite', this.inviteCheck.bind(this), 1);
|
||||
server.registerHook('in', 'join', this.joinCheck.bind(this), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming changenick command is invoked;
|
||||
* hook incoming changenick commands, reject them if the channel is 'purgatory'
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function changeNickCheck({
|
||||
socket, payload,
|
||||
}) {
|
||||
if (socket.channel === 'purgatory') { // @todo Multichannel update
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming whisper command is invoked;
|
||||
* hook incoming whisper commands, reject them if the channel is 'purgatory'
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function whisperCheck({
|
||||
socket, payload,
|
||||
}) {
|
||||
if (socket.channel === 'purgatory') { // @todo Multichannel update
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming chat command is invoked;
|
||||
* hook incoming chat commands, reject them if the channel is 'purgatory'
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function chatCheck({
|
||||
socket, payload,
|
||||
}) {
|
||||
if (socket.channel === 'purgatory') {
|
||||
if (isModerator(socket.level)) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming invite command is invoked;
|
||||
* hook incoming invite commands, reject them if the channel is 'purgatory'
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function inviteCheck({
|
||||
socket, payload,
|
||||
}) {
|
||||
if (socket.channel === 'purgatory') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an incoming join command is invoked;
|
||||
* hook incoming join commands, shunt them to purgatory if needed
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {(Object|boolean|string)} Object = same/altered payload,
|
||||
* false = suppress action,
|
||||
* string = error
|
||||
*/
|
||||
export function joinCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// check if target channel is locked
|
||||
if (typeof core.locked[payload.channel] === 'undefined' || core.locked[payload.channel] !== true) {
|
||||
if (payload.channel !== 'purgatory') {
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
// `join` is the legacy entry point, check if it needs to be upgraded
|
||||
if (typeof socket.hcProtocol === 'undefined') {
|
||||
payload = upgradeLegacyJoin(server, socket, payload);
|
||||
}
|
||||
|
||||
// store payload values
|
||||
const { channel, nick, pass } = payload;
|
||||
|
||||
// check if a client is able to join target channel
|
||||
const mayJoin = canJoinChannel(channel, socket);
|
||||
if (mayJoin !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You may not join that channel.',
|
||||
id: mayJoin,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// calling socket already in a channel
|
||||
// @todo multichannel update, will remove
|
||||
if (typeof socket.channel !== 'undefined') {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Remove this
|
||||
text: 'Joining more than one channel is not currently supported',
|
||||
id: Errors.Join.ALREADY_JOINED,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
// end todo
|
||||
|
||||
// validates the user input for `nick`
|
||||
if (verifyNickname(nick, socket) !== true) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Nickname must consist of up to 24 letters, numbers, and underscores',
|
||||
id: Errors.Join.INVALID_NICK,
|
||||
channel: false, // @todo Multichannel, false for global event
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// get trip and level
|
||||
const { trip, level } = getUserPerms(pass, core.saltKey, core.appConfig.data, channel);
|
||||
|
||||
// store the user values
|
||||
const userInfo = {
|
||||
nick,
|
||||
trip,
|
||||
uType: legacyLevelToLabel(level),
|
||||
hash: socket.hash,
|
||||
level,
|
||||
userid: socket.userid,
|
||||
isBot: socket.isBot,
|
||||
color: socket.color,
|
||||
channel,
|
||||
};
|
||||
|
||||
// check if trip is allowed
|
||||
if (userInfo.uType === 'user') {
|
||||
if (userInfo.trip == null || isTrustedUser(level) === false) {
|
||||
const origNick = userInfo.nick;
|
||||
const origChannel = payload.channel;
|
||||
|
||||
// not allowed, shunt to purgatory
|
||||
payload.channel = 'purgatory';
|
||||
|
||||
// lost souls have no names
|
||||
if (origChannel === 'purgatory') {
|
||||
// someone is pulling a Dante
|
||||
payload.nick = `Dante_${Math.random().toString(36).substr(2, 8)}`;
|
||||
} else {
|
||||
payload.nick = `${Math.random().toString(36).substr(2, 8)}${Math.random().toString(36).substr(2, 8)}`;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: danteQuotes[Math.floor(Math.random() * danteQuotes.length)],
|
||||
channel: 'purgatory', // @todo Multichannel
|
||||
}, socket);
|
||||
}, 100);
|
||||
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${payload.nick} is: ${origNick}\ntrip: ${userInfo.trip || 'none'}\ntried to join: ?${origChannel}\nhash: ${userInfo.hash}`,
|
||||
channel: 'purgatory', // @todo Multichannel, false for global info
|
||||
}, { channel: 'purgatory', level: isModerator });
|
||||
}
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} kick/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'lockroom',
|
||||
category: 'moderators',
|
||||
description: 'Locks a channel preventing default levels from joining',
|
||||
usage: `
|
||||
API: { cmd: 'lockroom', channel: '<optional channel, defaults to your current channel>' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
158
documentation/mod_speak.js.html
Normal file
158
documentation/mod_speak.js.html
Normal file
|
@ -0,0 +1,158 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: mod/speak.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: mod/speak.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/* eslint no-param-reassign: 0 */
|
||||
|
||||
/**
|
||||
* @author OpSimple ( https://github.com/OpSimple )
|
||||
* @summary Unmuzzle a user
|
||||
* @version 1.0.0
|
||||
* @description Pardon a dumb user to be able to speak again
|
||||
* @module speak
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Automatically executes once after server is ready
|
||||
* @param {Object} core - Reference to core enviroment object
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export function init(core) {
|
||||
if (typeof core.muzzledHashes === 'undefined') {
|
||||
core.muzzledHashes = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof payload.ip !== 'string' && typeof payload.hash !== 'string') {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: "hash:'targethash' or ip:'1.2.3.4' is required",
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
if (typeof payload.ip === 'string') {
|
||||
if (payload.ip === '*') {
|
||||
core.muzzledHashes = {};
|
||||
|
||||
return server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick} unmuzzled all users`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
}
|
||||
} else if (payload.hash === '*') {
|
||||
core.muzzledHashes = {};
|
||||
|
||||
return server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick} unmuzzled all users`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
}
|
||||
|
||||
// find target & remove mute status
|
||||
let target;
|
||||
if (typeof payload.ip === 'string') {
|
||||
target = server.getSocketHash(payload.ip);
|
||||
} else {
|
||||
target = payload.hash;
|
||||
}
|
||||
|
||||
delete core.muzzledHashes[target];
|
||||
|
||||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick}#${socket.trip} unmuzzled : ${target}`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} speak/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {Array} aliases - An array of alternative cmd names
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'speak',
|
||||
category: 'moderators',
|
||||
description: 'Pardon a dumb user to be able to speak again',
|
||||
aliases: ['unmuzzle', 'unmute'],
|
||||
usage: `
|
||||
API: { cmd: 'speak', ip/hash: '<target ip or hash>' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
142
documentation/mod_unban.js.html
Normal file
142
documentation/mod_unban.js.html
Normal file
|
@ -0,0 +1,142 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSDoc: Source: mod/unban.js</title>
|
||||
|
||||
<script src="scripts/prettify/prettify.js"> </script>
|
||||
<script src="scripts/prettify/lang-css.js"> </script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
||||
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<h1 class="page-title">Source: mod/unban.js</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section>
|
||||
<article>
|
||||
<pre class="prettyprint source linenums"><code>/**
|
||||
* @author Marzavec ( https://github.com/marzavec )
|
||||
* @summary Unban a user
|
||||
* @version 1.0.0
|
||||
* @description Un-bans target user by ip or hash
|
||||
* @module unban
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
} from '../utility/_UAC.js';
|
||||
|
||||
/**
|
||||
* Executes when invoked by a remote client
|
||||
* @param {Object} env - Enviroment object with references to core, server, socket & payload
|
||||
* @public
|
||||
* @return {void}
|
||||
*/
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket, 10);
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof payload.ip !== 'string' && typeof payload.hash !== 'string') {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: "hash:'targethash' or ip:'1.2.3.4' is required",
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// find target
|
||||
let mode;
|
||||
let target;
|
||||
if (typeof payload.ip === 'string') {
|
||||
mode = 'ip';
|
||||
target = payload.ip;
|
||||
} else {
|
||||
mode = 'hash';
|
||||
target = payload.hash;
|
||||
}
|
||||
|
||||
// remove arrest record
|
||||
server.police.pardon(target);
|
||||
|
||||
// mask ip if used
|
||||
if (mode === 'ip') {
|
||||
target = server.getSocketHash(target);
|
||||
}
|
||||
console.log(`${socket.nick} [${socket.trip}] unbanned ${target} in ${socket.channel}`);
|
||||
|
||||
// reply with success
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
text: `Unbanned ${target}`,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
// notify mods
|
||||
server.broadcast({
|
||||
cmd: 'info',
|
||||
text: `${socket.nick}#${socket.trip} unbanned: ${target}`,
|
||||
channel: false, // @todo Multichannel, false for global
|
||||
}, { level: isModerator });
|
||||
|
||||
// stats are fun
|
||||
core.stats.decrement('users-banned');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module meta information
|
||||
* @public
|
||||
* @typedef {Object} unban/info
|
||||
* @property {string} name - Module command name
|
||||
* @property {string} category - Module category name
|
||||
* @property {string} description - Information about module
|
||||
* @property {string} usage - Information about module usage
|
||||
*/
|
||||
export const info = {
|
||||
name: 'unban',
|
||||
category: 'moderators',
|
||||
description: 'Un-bans target user by ip or hash',
|
||||
usage: `
|
||||
API: { cmd: 'unban', ip/hash: '<target ip or hash>' }`,
|
||||
};
|
||||
</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-addmod.html">addmod</a></li><li><a href="module-ban.html">ban</a></li><li><a href="module-changecolor.html">changecolor</a></li><li><a href="module-changenick.html">changenick</a></li><li><a href="module-chat.html">chat</a></li><li><a href="module-disablecaptcha.html">disablecaptcha</a></li><li><a href="module-disconnect.html">disconnect</a></li><li><a href="module-dumb.html">dumb</a></li><li><a href="module-emote.html">emote</a></li><li><a href="module-enablecaptcha.html">enablecaptcha</a></li><li><a href="module-forcecolor.html">forcecolor</a></li><li><a href="module-help.html">help</a></li><li><a href="module-invite.html">invite</a></li><li><a href="module-join.html">join</a></li><li><a href="module-kick.html">kick</a></li><li><a href="module-listusers.html">listusers</a></li><li><a href="module-lockroom.html">lockroom</a></li><li><a href="module-morestats.html">morestats</a></li><li><a href="module-ping.html">ping</a></li><li><a href="module-reload.html">reload</a></li><li><a href="module-removemod.html">removemod</a></li><li><a href="module-saveconfig.html">saveconfig</a></li><li><a href="module-session.html">session</a></li><li><a href="module-shout.html">shout</a></li><li><a href="module-socketreply.html">socketreply</a></li><li><a href="module-speak.html">speak</a></li><li><a href="module-stats.html">stats</a></li><li><a href="module-unban.html">unban</a></li><li><a href="module-unbanall.html">unbanall</a></li><li><a href="module-unlockroom.html">unlockroom</a></li><li><a href="module-updateMessage.html">updateMessage</a></li><li><a href="module-whisper.html">whisper</a></li></ul>
|
||||
</nav>
|
||||
|
||||
<br class="clear">
|
||||
|
||||
<footer>
|
||||
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Fri Dec 29 2023 23:22:35 GMT-0800 (Pacific Standard Time)
|
||||
</footer>
|
||||
|
||||
<script> prettyPrint(); </script>
|
||||
<script src="scripts/linenumber.js"> </script>
|
||||
</body>
|
||||
</html>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user