From 23dd42c963f2bbcc8811bb71e3b22f3c6286f7f5 Mon Sep 17 00:00:00 2001 From: hgw Date: Mon, 2 Oct 2023 02:15:55 +0000 Subject: [PATCH] init --- .gitignore | 2 + Dockerfile | 9 +++ LICENSE | 15 +++++ README.md | 22 +++++++ bot.js | 118 +++++++++++++++++++++++++++++++++++++ commands/example1.js | 43 ++++++++++++++ config/example.config.json | 32 ++++++++++ docker-compose.yml | 8 +++ package-lock.json | 52 ++++++++++++++++ package.json | 19 ++++++ 10 files changed, 320 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bot.js create mode 100644 commands/example1.js create mode 100644 config/example.config.json create mode 100644 docker-compose.yml create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8cc2f79 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +config/config.json \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..82f9d5d --- /dev/null +++ b/Dockerfile @@ -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" ] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..221d2dd --- /dev/null +++ b/LICENSE @@ -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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..93d4e0d --- /dev/null +++ b/README.md @@ -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 \ No newline at end of file diff --git a/bot.js b/bot.js new file mode 100644 index 0000000..97ee04d --- /dev/null +++ b/bot.js @@ -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(" ")) + } +}); \ No newline at end of file diff --git a/commands/example1.js b/commands/example1.js new file mode 100644 index 0000000..0bda2a1 --- /dev/null +++ b/commands/example1.js @@ -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. +// ###################################################################################### \ No newline at end of file diff --git a/config/example.config.json b/config/example.config.json new file mode 100644 index 0000000..306201d --- /dev/null +++ b/config/example.config.json @@ -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" + } +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..687848a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3.3' +services: + bones: + container_name: bones + build: . + restart: always + volumes: + - './config:/home/node/app/config' \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..83b47cc --- /dev/null +++ b/package-lock.json @@ -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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fd515be --- /dev/null +++ b/package.json @@ -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" + } +}