/* 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); } }