Create bots

This commit is contained in:
hiimgoodpack 2021-01-25 00:23:34 -05:00
parent 0972fb4bde
commit 60b564a154
Signed by: hiimgoodpack
GPG key ID: 4E0E62733C14AE69
6 changed files with 379 additions and 64 deletions

154
bots/ChanServ.js Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

47
bots/DinoServ.js Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
});
}
}

31
bots/MsgServ.js Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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
}
};

100
bots/NickServ.js Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
});
}
};

View file

@ -1,33 +1,42 @@
module.exports = { module.exports = {
server: "127.0.0.1:3005", 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" + motd: "A Brainlet to IRC translation layer\n" +
"SECURITY WARNING: The traffic to the Brainlet server is not encrypted.\n" + "SECURITY WARNING: The traffic to the Brainlet server is not encrypted\n" +
"Use \"/msg NickServ help\" to see how to use an account", "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: { responses: {
NickServ: { 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 <command> <arguments>" You can use a command with "/msg NickServ <command> <arguments>"
Commands: Commands:
HELP - Shows this message HELP - Shows this message
REGISTER <password> <email> - Creates an account, using your current nick as the username. REGISTER <password> <email> - 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. Note: The email is not directly used by the IRC server, but could be used by the Brainlet host
IDENTIFY <name> <password> - Logs into an account.`, IDENTIFY <name> <password> - Logs into an account`,
// Sent on invalid command // Sent on invalid command
invalid: "Invalid command. Use \"/msg NickServ help\" for help", 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 <command> <arguments>
// Sent on an invalid entry. Commands:
// %s is replaced with how it's invalid. HELP - Shows this message
invalidName: "Invalid username specified. %s", INVITE - Get invited to every channel, as each channel is invite only
invalidEmail: "Invalid email specified. %s", CREATE <category name> - Creates a category`,
invalidPassword: "Invalid password specified. %s"
} // 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"]
} }

View file

@ -19,6 +19,7 @@ const fs = require("fs");
const ircServer = require("./ircd.js/lib/server.js"); const ircServer = require("./ircd.js/lib/server.js");
const ircClient = require("irc"); const ircClient = require("irc");
const crypto = require("crypto"); const crypto = require("crypto");
const brainlet = require("./brainlet-lib/index.js");
// Set up IRC server // Set up IRC server
const config = require(`${__dirname}/config.js`) const config = require(`${__dirname}/config.js`)
@ -28,11 +29,19 @@ const genPass = () => {
return crypto.randomBytes(66).toString("base64"); return crypto.randomBytes(66).toString("base64");
} }
// NOTE: You don't actually need the password to log into the account
let systemUsers = { let systemUsers = {
NickServ: { NickServ: {
password: "12345"
},
ChanServ: {
password: genPass()
},
DinoServ: {
password: genPass() password: genPass()
} }
} }
const ircConfig = { const ircConfig = {
network: "BrainletToIRC", network: "BrainletToIRC",
hostname: "localhost", hostname: "localhost",
@ -61,51 +70,16 @@ fs.writeFileSync(`${__dirname}/ircd.js/config/config.json`, JSON.stringify(ircCo
ircServer.Server.boot(); ircServer.Server.boot();
// Set up system users // Set up system users
const ircServerAddress = ircConfig.links.server.host; let tokens = {};
const ircServerPort = ircConfig.links.server.port; 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"); for (const botIndex in config.bots) {
console.log(NickServ) const bot = config.bots[botIndex];
NickServ.addListener("error", console.error); require(`./bots/${bot}.js`).start(config, {tokens, sockets, availableBots, channelNameToCategoryId, categoryIdToChannelName});
NickServ.addListener("message", (from, to, text, message) => { availableBots[bot] = true;
// 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;
}
}
});