diff --git a/bots/ChanServ.js b/bots/ChanServ.js
new file mode 100644
index 0000000..80e0bc9
--- /dev/null
+++ b/bots/ChanServ.js
@@ -0,0 +1,154 @@
+/*
+ An IRC bot to sync Brainlet categories
+ Copyright (C) 2020 hiimgoodpack
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+const ircClient = require("irc");
+// Node's replaceAll is a new feature. We'll use a package instead
+const replaceAll = require("replaceall");
+const brainlet = require("../brainlet-lib/index.js");
+
+module.exports = {
+ start: (config, {tokens, sockets, availableBots, channelNameToCategoryId, categoryIdToChannelName}) => {
+ let bot = new ircClient.Client("localhost", "ChanServ", {
+ realName: "A bot to sync channels from a Brainlet instance to the IRC server"
+ });
+
+ const getRandomToken = () => {
+ const names = Object.keys(tokens);
+ let nameIndex = Math.floor(Math.random() * names.length);
+ // Floating point rounding might make nameIndex >= tokens.length, which we don't want
+ if (nameIndex >= tokens.length)
+ nameIndex = tokens.length-1;
+
+ return tokens[names[nameIndex]];
+ }
+
+ const commands = {
+ help: (from) => {
+ bot.notice(from, config.responses.ChanServ.help);
+ },
+ invite: (from) => {
+ if (!tokens[from]) {
+ bot.notice(from, config.responses.ChanServ.accessDenied);
+ return;
+ }
+ Object.keys(channelNameToCategoryId).forEach((channelName) => {
+ bot.send("INVITE", from, channelName);
+ })
+ },
+ create: (from, args) => {
+ if (!tokens[from]) {
+ bot.notice(from, config.responses.ChanServ.accessDenied);
+ return;
+ }
+ const categoryTitle = args.splice(1).join(" ");
+
+ bot.notice(from, `Creating category ${categoryTitle}...`);
+ brainlet.categories.create(config.server, tokens[from], categoryTitle).then(() => {
+ bot.notice(from, "Successfully created category on Brainlet\n" +
+ "You may need to wait a few seconds for a category sync to occur to use the channel");
+ }).catch((error) => {
+ bot.notice(from, `Failed to create category: ${error}`);
+ });
+ }
+ };
+
+ bot.addListener("error", console.error);
+ bot.addListener("message", (from, channel, text, message) => {
+ // Make sure it's a private message channel the bot
+ if (channel !== bot.opt.nick)
+ return;
+
+ const args = text.split(" ");
+ if (args.length == 0) {
+ bot.notice(from, config.responses.ChanServ.invalid);
+ return;
+ }
+
+ const command = commands[args[0].toLowerCase()];
+ if (!command) {
+ bot.notice(from, config.responses.ChanServ.invalid);
+ return;
+ }
+
+ command(from, args);
+ });
+ bot.addListener("join", (channel, nick) => {
+ bot.send("MODE", channel, "+v", nick);
+
+ // Is it a bot?
+ if (availableBots[nick]) {
+ // Give it channel op
+ bot.send("MODE", channel, "+o", nick);
+ } else {
+ const socket = sockets[nick];
+ const categoryId = channelNameToCategoryId[channel];
+ console.log(socket);
+ socket.connect(categoryId).then(() => {
+ console.log("Successfully joined channel");
+ });
+ }
+ });
+
+ // We need 2 hexadecimal digits for a buffer to work
+ let nextId = 0xF+1;
+ const categoryToChannelName = (name) => {
+ return `#${replaceAll(" ", "_", name)}_${Buffer.from((nextId++).toString(16), "hex").toString("base64")}`;
+ };
+ // Update category list
+ setInterval(() => {
+ const token = getRandomToken();
+ if (!token)
+ return;
+ // TODO: Handle having more than 100 categories
+ // Alternatively, send a notice if there is more than 100 categories
+ const maxCategories = 100;
+ brainlet.categories.list(config.server, token, maxCategories).then((categories) => {
+ for (const {_id: categoryId, title: categoryTitle, creator: {username: creatorUsername}} of categories) {
+ // Make sure this is a new channel
+ if (categoryIdToChannelName[categoryId])
+ continue;
+
+ const channelName = categoryToChannelName(categoryTitle);
+
+ bot.join(channelName, () => {
+ channelNameToCategoryId[channelName] = categoryId;
+ categoryIdToChannelName[categoryId] = channelName;
+ bot.send("TOPIC", channelName, `${ircClient.colors.wrap("light_blue", categoryTitle)} by ${ircClient.colors.wrap("light_red", creatorUsername)}`);
+
+ // Invite only
+ bot.send("MODE", channelName, "+i");
+ // Allow mutes
+ bot.send("MODE", channelName, "+m");
+
+ // Invite everyone who is logged in
+ Object.keys(tokens).forEach((nick) => {
+ bot.send("INVITE", nick, channelName);
+ });
+ Object.keys(availableBots).forEach((nick) => {
+ if (nick === "ChanServ")
+ return;
+ bot.send("INVITE", nick, channelName);
+ });
+ });
+ }
+ }).catch((error) => {
+ console.error(`An error occured when updating category list: ${error}`);
+ });
+ }, 5000);
+ }
+}
diff --git a/bots/DinoServ.js b/bots/DinoServ.js
new file mode 100644
index 0000000..ccdd177
--- /dev/null
+++ b/bots/DinoServ.js
@@ -0,0 +1,47 @@
+/*
+ An IRC bot to annoy people
+ Copyright (C) 2020 hiimgoodpack
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+const ircClient = require("irc");
+const brainlet = require("../brainlet-lib/index.js");
+
+module.exports = {
+ start: (config, {tokens}) => {
+ let bot = new ircClient.Client("localhost", "DinoServ", {
+ realName: "A bot to annoy users"
+ });
+
+ bot.addListener("error", console.error);
+ bot.addListener("message", (from, channel, text, message) => {
+ if (text.match(/apt-get/))
+ bot.say(channel, `Hello, annoying bot reporting in, just notifying @${from} that the apt-get command on debian is deprecated. Please use apt instead.
+
+Thank you for attending my TED-talk.`);
+ if (text.match(/reverse engineer/i)) {
+ bot.say(channel, `@${from} Watch your language.`);
+ bot.send("MODE", channel, "-v", from);
+ bot.notice(from, "You have been muted for 10 seconds.");
+ setTimeout(() => {
+ bot.send("MODE", channel, "+v", from);
+ }, 10000);
+ }
+ });
+ bot.addListener("invite", (channel) => {
+ bot.join(channel);
+ });
+ }
+}
diff --git a/bots/MsgServ.js b/bots/MsgServ.js
new file mode 100644
index 0000000..8bea17e
--- /dev/null
+++ b/bots/MsgServ.js
@@ -0,0 +1,31 @@
+/*
+ An IRC bot manager to sync Brainlet messages
+ Copyright (C) 2020 hiimgoodpack
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+const ircClient = require("irc");
+// Node's replaceAll is a new feature. We'll use a package instead
+const replaceAll = require("replaceall");
+const brainlet = require("../brainlet-lib/index.js");
+
+module.exports = {
+ start: (config, {tokens, sockets, channelNameToCategoryId, categoryIdToChannelName}) => {
+ let bot = new ircClient.Client("localhost", "MsgServ", {
+ realName: "A bot to sync messages from a Brainlet instance to the IRC server"
+ });
+ // TODO: Actually do stuff
+ }
+};
diff --git a/bots/NickServ.js b/bots/NickServ.js
new file mode 100644
index 0000000..c6cc88a
--- /dev/null
+++ b/bots/NickServ.js
@@ -0,0 +1,100 @@
+/*
+ An IRC bot to manage Brainlet accounts
+ Copyright (C) 2020 hiimgoodpack
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+const ircClient = require("irc");
+const brainlet = require("../brainlet-lib/index.js");
+
+module.exports = {
+ start: (config, {tokens, sockets}) => {
+ let bot = new ircClient.Client("localhost", "NickServ", {
+ realName: "A bot to let users manage Brainlet accounts from an IRC server"
+ });
+
+ const commands = {
+ help: (from) => {
+ bot.notice(from, config.responses.NickServ.help);
+ },
+ register: (from, [command, password, email]) => {
+ if (!password || !email) {
+ bot.notice(from, config.responses.NickServ.invalid);
+ return;
+ }
+
+ bot.notice(from, `Registering account ${from}...`);
+ brainlet.users.create(config.server, {
+ name: from,
+ email: email,
+ password: password
+ }).then(() => {
+ bot.notice(from, "Successfully registered account");
+ commands.identify(from, ["identify", from, password]);
+ }).catch((error) => {
+ bot.notice(from, `Failed to register account: ${error}`);
+ });
+ },
+ identify: (from, [command, name, password]) => {
+ if (!name || !password) {
+ bot.notice(from, config.responses.NickServ.invalid);
+ return;
+ }
+
+ bot.notice(from, `Logging into account ${name}...`);
+ brainlet.users.login(config.server, {
+ name: name,
+ password: password
+ }).then(({token = ""}) => {
+ bot.notice(from, "Successfully logged into account");
+ tokens[from] = token;
+
+ bot.notice(from, "Creating websocket connection...");
+ brainlet.categories.client(config.server, token).then((client) => {
+ sockets[from] = client;
+ bot.notice(from, "Successfully connected to server\n" +
+ "You will likely want to use \"/msg ChanServ INVITE\" now\n" +
+ "Use \"/msg ChanServ HELP\" for more information");
+ }).catch((error) => {
+ bot.notice(from, `Failed to create websocket connection: ${error}`);
+ });
+ }).catch((error) => {
+ bot.notice(from, `Failed to log into account: ${error}`);
+ });
+ }
+ };
+
+ bot.addListener("error", console.error);
+ bot.addListener("message", (from, channel, text, message) => {
+ // Make sure it's a private message channel the bot
+ if (channel !== bot.opt.nick)
+ return;
+
+ const args = text.split(" ");
+ if (args.length == 0) {
+ bot.notice(from, config.responses.NickServ.invalid);
+ return;
+ }
+
+ const command = commands[args[0].toLowerCase()];
+ if (!command) {
+ bot.notice(from, config.responses.NickServ.invalid);
+ return;
+ }
+
+ command(from, args);
+ });
+ }
+};
diff --git a/config.js b/config.js
index 0c2244d..65b1d0f 100644
--- a/config.js
+++ b/config.js
@@ -1,33 +1,42 @@
module.exports = {
server: "127.0.0.1:3005",
- spectator: {
- name: "BrainletToIrcSpectator",
- password: "Put a secure password here. See the README for more info"
- },
- motd: "A Brainlet to IRC translation layer.\n" +
- "SECURITY WARNING: The traffic to the Brainlet server is not encrypted.\n" +
- "Use \"/msg NickServ help\" to see how to use an account",
+ motd: "A Brainlet to IRC translation layer\n" +
+ "SECURITY WARNING: The traffic to the Brainlet server is not encrypted\n" +
+ "Use \"/msg NickServ help\" to see how to use an account\n" +
+ "Use \"/msg ChanServ help\" to see how to join and manage channels",
responses: {
NickServ: {
- help: `NickServ allows you to use and create a Brainlet account from IRC.
+ help: `NickServ allows you to use and create a Brainlet account from the IRC server
You can use a command with "/msg NickServ "
Commands:
HELP - Shows this message
-REGISTER - Creates an account, using your current nick as the username.
- Note: The email is not directly used by IRC, but may be used by the Brainlet host.
-IDENTIFY - Logs into an account.`,
+REGISTER - Creates an account, using your current nick as the username
+ Note: The email is not directly used by the IRC server, but could be used by the Brainlet host
+IDENTIFY - Logs into an account`,
// Sent on invalid command
invalid: "Invalid command. Use \"/msg NickServ help\" for help",
+ },
+ ChanServ: {
+ help: `ChanServ syncs channel information between a Brainlet instance and the IRC server
+You may get channel invites from ChanServ, as each channel is invite-only to require you to log in
+You can use a command with "/msg ChanServ
- // Sent on an invalid entry.
- // %s is replaced with how it's invalid.
- invalidName: "Invalid username specified. %s",
- invalidEmail: "Invalid email specified. %s",
- invalidPassword: "Invalid password specified. %s"
+Commands:
+HELP - Shows this message
+INVITE - Get invited to every channel, as each channel is invite only
+CREATE - Creates a category`,
+
+ // Sent on invalid command
+ invalid: "Invalid command. Use \"/msg ChanServ help\" for help",
+
+ // Sent when using a command requiring authentication without being logged in
+ accessDenied: "You must log in before using this command. Use \"/msg NickServ help\" to see how to log in"
}
- }
+ },
+
+ bots: ["NickServ", "ChanServ", "MsgServ"]
}
diff --git a/index.js b/index.js
index b1062b5..05df6d5 100644
--- a/index.js
+++ b/index.js
@@ -19,6 +19,7 @@ const fs = require("fs");
const ircServer = require("./ircd.js/lib/server.js");
const ircClient = require("irc");
const crypto = require("crypto");
+const brainlet = require("./brainlet-lib/index.js");
// Set up IRC server
const config = require(`${__dirname}/config.js`)
@@ -28,11 +29,19 @@ const genPass = () => {
return crypto.randomBytes(66).toString("base64");
}
+// NOTE: You don't actually need the password to log into the account
let systemUsers = {
NickServ: {
+ password: "12345"
+ },
+ ChanServ: {
+ password: genPass()
+ },
+ DinoServ: {
password: genPass()
}
}
+
const ircConfig = {
network: "BrainletToIRC",
hostname: "localhost",
@@ -61,51 +70,16 @@ fs.writeFileSync(`${__dirname}/ircd.js/config/config.json`, JSON.stringify(ircCo
ircServer.Server.boot();
// Set up system users
-const ircServerAddress = ircConfig.links.server.host;
-const ircServerPort = ircConfig.links.server.port;
+let tokens = {};
+let sockets = {};
+let availableBots = {};
+// Maps channel names to category ids
+let channelNameToCategoryId = {};
+// Maps category ids to channel names
+let categoryIdToChannelName = {};
-let NickServ = new ircClient.Client("localhost", "NickServ");
-console.log(NickServ)
-NickServ.addListener("error", console.error);
-NickServ.addListener("message", (from, to, text, message) => {
- // Make sure it's a private message to NickServ
- if (to !== NickServ.opt.nick)
- return;
-
- const args = text.split(" ");
- if (args.length == 0) {
- NickServ.notice(from, config.responses.NickServ.invalid);
- return;
- }
-
- switch (args[0].toLowerCase()) {
- case "help": {
- NickServ.notice(from, config.responses.NickServ.help);
- break;
- }
- case "register": {
- const password = args[1];
- const email = args[2];
- if (!password || !email) {
- NickServ.notice(from, config.responses.NickServ.invalid);
- return;
- }
- NickServ.notice(from, "Account registration is currently not implemented");
- break;
- }
- case "identify": {
- const name = args[1];
- const password = args[2];
- if (!name || !password) {
- NickServ.notice(from, config.responses.NickServ.invalid);
- return;
- }
- NickServ.notice(from, "Account login is currently not implemented");
- break;
- }
- default: {
- NickServ.notice(from, config.responses.NickServ.invalid);
- break;
- }
- }
-});
+for (const botIndex in config.bots) {
+ const bot = config.bots[botIndex];
+ require(`./bots/${bot}.js`).start(config, {tokens, sockets, availableBots, channelNameToCategoryId, categoryIdToChannelName});
+ availableBots[bot] = true;
+}