This commit is contained in:
hgw 2023-10-02 02:15:55 +00:00
commit 23dd42c963
10 changed files with 320 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules/
config/config.json

9
Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM node:18-alpine3.17
RUN mkdir -p /home/node/app/node_modules \
&& chown -R node:node /home/node/app
WORKDIR /home/node/app
USER node
COPY --chown=node:node package*.json ./
RUN npm i
COPY --chown=node:node . .
CMD [ "node", "bot.js" ]

15
LICENSE Normal file
View File

@ -0,0 +1,15 @@
ISC License
Copyright (c) 2023 hgw7
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

22
README.md Normal file
View File

@ -0,0 +1,22 @@
# Bones
#### node.js IRC bot framework
Bones is the base framework I use for creating multithreaded IRC bots, originally developed as a way to hugely improve on the speeds of earlier versions of mercury (which were not multithreaded and was very slow)
## Deployment
Instructions are general and assume you have already developed something with this framework, not a whole lot of point trying to run this as it is.
1. Have Docker (required) and Docker Compose (optional, but strongly recommended, this guide assumes you have it) installed already.
2. Rename `config/example.default.json` to `config/default.json` and modify it accordingly. A list of variables and their descriptions can be found in this repos wiki. You do not need to do anything with `example.usersettings.json` unless you wish to predefine settings prior to the bots first start, the usersettings file will be made on the first run if it does not exist.
3. You may also want to edit the container names in the `docker-compose.yml` file accordingly. (Optional but recommended)
4. Run `docker compose up` to begin. Append `-d` to start in the background and `--build` for the first run and subsequent starts after edits have been made. If you begin the bot with `-d` you can run `docker compose logs -f` to see live logs.
## Support
If you need assistance with installation or usage, you are more than welcome to contact me in #5000 on `irc.supernets.org`
## License
This framework is licensed under the ISC License

118
bot.js Normal file
View File

@ -0,0 +1,118 @@
// Bones node.js IRC bot framework - git.supernets.org/hgw/bones
// __
// / /_ ____ ____ ___ _____
// / __ \/ __ \/ __ \/ _ \/ ___/
// / /_/ / /_/ / / / / __(__ )
// /_.___/\____/_/ /_/\___/____/
//
var config = require('./config/default.json');
var irc = require("irc");
var fs = require("fs");
const { Worker } = require('worker_threads');
warningMsg = ''+config.colours.brackets+'['+config.colours.warning+'WARNING'+config.colours.brackets+']'
errorMsg = ''+config.colours.brackets+'['+config.colours.error+'ERROR'+config.colours.brackets+']'
const msgTimeout = new Set();
const msgTimeoutMsg = new Set();
const timer = ms => new Promise(res => setTimeout(res, ms))
var hostmask = null
var bot = new irc.Client(config.irc.server, config.irc.nickname, {
channels: config.irc.channels,
secure: config.irc.ssl,
port: config.irc.port,
autoRejoin: config.irc.autorejoin,
userName: config.irc.username,
realName: config.irc.realname,
floodProtection: config.floodprotect.flood_protection,
floodProtectionDelay: config.floodprotect.flood_protection_delay
});
// We use consoleLog instead of console.log() as it allows togglable logging through the config file.
// If you have things you definitely do not want to be togglable then you can continue using console.log()
function consoleLog(log) {
if (config.misc.logging === "true") {
console.log(log)
} else {
return;
}
}
function openPostWorker(chan, command, d1, d2, d3, d4, d5, d6) {
consoleLog(`[bot.openPostWorker] Opening ${command} worker`)
const worker = new Worker(`./commands/${command}.js`, {
workerData: {
d1, d2, d3, d4, d5, d6 //d1-d6 equate to variables you can pass in to a worker, see the example1 block below for an example (var1 there is d1 here). Further defined in individual command files.
}
});
worker.once('message', (string) => {
consoleLog(`[bot.openPostWorker.finalising] Got output from ${command}, sending to `+chan);
bot.say(chan, string);
});
}
// Example command function.
// In this example "channel" is the channel the prompt came from and "var1" is the contents of
// that message after the !example1 part, as called in the listener further down
async function example1(channel, var1) {
if (sub != undefined ) {
var sub = var1.toLowerCase() //transform variables to lowercase, this is not a requirement of course if you have case-sensitive inputs
}
openPostWorker(channel, 'example1', var1) //Opens commands/example1.js as a seperate process, much faster than just running the command in the one file
}
// ###################################
// Put your bot command functions here
// ###################################
bot.addListener('message', function(nick, to, text, from) {
// nick = Nickname of the message sender
// to = Channel the message came from
// text = Contents of the message received by the bot
if (text.startsWith(config.irc.prefix)) {
if (msgTimeout.has(to)) {
if (msgTimeoutMsg.has("yes")) {
return;
} else {
bot.say(to, errorMsg+" You are sending commands too quickly")
msgTimeoutMsg.add("yes");
setTimeout(() => {
msgTimeoutMsg.delete("yes");
}, config.floodprotect.command_listen_timeout)
}
} else {
var args = text.split(' ');
var command = args[0].toLowerCase()
if (command === config.irc.prefix+'example1') {
example1(to, text)
} else if (command === config.irc.prefix+'COMMAND2') {
if (args[1] == undefined ) {
//If you have a help function you can call it here, otherwise catch a no input error
} else {
//Call your command here
}
} else if (command === config.irc.prefix+'COMMAND2') {
if (args[1] == undefined ) {
//If you have a help function you can call it here, otherwise catch a no input error
} else {
//Call your command here
}
}
msgTimeout.add(to);
setTimeout(() => {
msgTimeout.delete(to);
}, config.floodprotect.command_listen_timeout)
}
}
});
bot.addListener('error', function(message) {
consoleLog('[ERROR]' +message) //Dump errors to console
});
process.on('uncaughtException', function (err) {
console.error(err);
if (config.errorhandling.log_errors_to_irc == 'true') { //If logging errors to IRC is enabled then we send the error to that, otherwise we only consoleLog it
bot.say(config.errorhandling.error_channel, errorMsg+" "+err.stack.split('\n',1).join(" "))
}
});

43
commands/example1.js Normal file
View File

@ -0,0 +1,43 @@
const config = require('../config/default.json')
const { parentPort, workerData } = require('worker_threads');
const { d1, d2 } = workerData; //Declare all used variables here (if you only pass 1 variable to this command you only really need d1 in here, but it doesnt matter)
var var1 = d1; // Declaring d1 as var1, just for consistancy with the last file but again, this may not be necessary in all cases.
var val2 = d2; // Further variables declared like so, fairly simple
const timer = ms => new Promise(res => setTimeout(res, ms))
warningMsg = ''+config.colours.brackets+'['+config.colours.warning+'WARNING'+config.colours.brackets+']'
errorMsg = ''+config.colours.brackets+'['+config.colours.error+'ERROR'+config.colours.brackets+']'
// We use consoleLog instead of console.log() as it allows togglable logging through the config file.
// If you have things you definitely do not want to be togglable then you can continue using console.log()
function consoleLog(log) {
if (config.misc.logging === "true") {
console.log(log)
} else {
return;
}
}
// the errorMessage function is just a nice-to-have if you want to use error codes internally to just push set messages to console/IRC
// You can also just sendUpstream('ERROR_MESSAGE_HERE') to handle errors as well if wanted,
function errorMessage(error, code, extra) {
consoleLog('[example1.errorMessage] '+error.code)
if (code == "BAD") {
var error = errorMsg+" SHITS_FUCKED_MAN: " + extra + " not found"
} else {
var error = errorMsg+" Unknown error"
}
parentPort.postMessage(error);
process.exit()
}
// All code must end with either a sendUpstream or errorMessage call, otherwise this subprocess will remain running indefinitely.
// sendUpstream takes the content provided to it and sends it in whatever channel the original command prompt came from.
async function sendUpstream(content) {
parentPort.postMessage(content);
process.exit()
}
// ######################################################################################
// Put your commands code here, remember to always end with sendUpstream or errorMessage.
// ######################################################################################

View File

@ -0,0 +1,32 @@
{
"irc": {
"server": "irc.supernets.org",
"port": 6697,
"ssl": "true",
"channels": [
"#5000"
],
"nickname": "bones-dev",
"username": "bones-dev",
"realname": "git.supernets.org/hgw/bones",
"autorejoin": "true",
"prefix": "!"
},
"floodprotect": {
"flood_protection": "false",
"flood_protection_delay": "0",
"command_listen_timeout": "2500"
},
"errorhandling": {
"error_logging": "true",
"error_channel": "#CHANNELTOSENDERRORSTO",
"error_channel_pass": ""
},
"colours": {
"warning": "08",
"error": "04"
},
"misc": {
"logging": "true"
}
}

8
docker-compose.yml Normal file
View File

@ -0,0 +1,8 @@
version: '3.3'
services:
bones:
container_name: bones
build: .
restart: always
volumes:
- './config:/home/node/app/config'

52
package-lock.json generated Normal file
View File

@ -0,0 +1,52 @@
{
"name": "bones",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"fs": {
"version": "0.0.1-security",
"resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
"integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w=="
},
"iconv": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/iconv/-/iconv-2.2.3.tgz",
"integrity": "sha512-evIiYeKdt5nEGYKNkQcGPQy781sYgbBKi3gEkt1s4CwteCdOHSjGGRyyp6lP8inYFZwvzG3lgjXEvGUC8nqQ5A==",
"optional": true,
"requires": {
"nan": "^2.3.5"
}
},
"irc": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/irc/-/irc-0.5.2.tgz",
"integrity": "sha512-KnrvkV05Y71SWmRWHtnlWEIH7LA/YeDul6l7tncCGLNEw4B6Obtmkatb3ACnSLj0kOJ6UBiuhss9e+eRG3zlxw==",
"requires": {
"iconv": "~2.2.1",
"irc-colors": "^1.1.0",
"node-icu-charset-detector": "~0.2.0"
}
},
"irc-colors": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/irc-colors/-/irc-colors-1.5.0.tgz",
"integrity": "sha512-HtszKchBQTcqw1DC09uD7i7vvMayHGM1OCo6AHt5pkgZEyo99ClhHTMJdf+Ezc9ovuNNxcH89QfyclGthjZJOw=="
},
"nan": {
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz",
"integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==",
"optional": true
},
"node-icu-charset-detector": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/node-icu-charset-detector/-/node-icu-charset-detector-0.2.0.tgz",
"integrity": "sha512-DYOFJ3NfKdxEi9hPbmoCss6WydGhJsxpSleUlZfAWEbZt3AU7JuxailgA9tnqQdsHiujfUY9VtDfWD9m0+ThtQ==",
"optional": true,
"requires": {
"nan": "^2.3.3"
}
}
}
}

19
package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "bones",
"version": "1.0.0",
"description": "node.js IRC bot framework",
"main": "bot.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://git.supernets.org/hgw/bones"
},
"author": "hgw7",
"license": "ISC",
"dependencies": {
"fs": "0.0.1-security",
"irc": "^0.5.2"
}
}