diff --git a/README.md b/README.md index 98896ad..3b6e348 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,4 @@ $ cp config.example.json config.json $ cp blacklist.example.json blacklist.json ``` 3. Fill out the entries in `config.json` -4. Run the bot using `node bot.js` or `npm start` \ No newline at end of file +4. Run the bot using `node bot.js` or `npm start` diff --git a/blacklist.example.json b/blacklist.example.json index 0637a08..9e26dfe 100644 --- a/blacklist.example.json +++ b/blacklist.example.json @@ -1 +1 @@ -[] \ No newline at end of file +{} \ No newline at end of file diff --git a/bot.js b/bot.js index 6ca87d2..9b356a4 100644 --- a/bot.js +++ b/bot.js @@ -3,13 +3,14 @@ const fs = require("fs"); const irc = require("irc"); const Summon = require("./spells/summon"); +// const Banish = require("./spells/summon"); const botName = "BabiliBot" const client = new irc.Client("localhost", botName, { channels: [ "#bots" ], - localAddress: "0.0.0.0", + localAddress: "127.0.0.1", port: 6667, userName: "aewens", floodProtection: true, @@ -21,12 +22,14 @@ const client = new irc.Client("localhost", botName, { }); client.config = JSON.parse(fs.readFileSync("config.json", "utf8")); +client.config.botName = botName; client.spells = [ - Summon + Summon, + // Banish ]; -// NOTE - Once the #chaos throne is returned to it's king, this will remain +// NOTE - For my personal copy, jan6 will remain here until #chaos is mine client.blacklist = JSON.parse(fs.readFileSync("blacklist.json", "utf8")); // Format: @@ -41,7 +44,7 @@ const contains = (text, test) => { } const logBlacklisters = (from, message) => { - const blacklistLog = `${from} tried to issue ${message}`; + const blacklistLog = `${from} tried to issue ${message}\n`; fs.appendFile("blacklist.log", blacklistLog, (err) => { if (err) throw err; console.log("Blacklist", blacklistLog); @@ -50,14 +53,17 @@ const logBlacklisters = (from, message) => { const addBlacklister = (from, message) => { // Log reason for adding to the blacklist - const blacklistReason = `${from} was added for spamming ${message}`; + const blacklistReason = `${from} was added for spamming ${message}\n`; fs.appendFile("blacklisters.log", blacklistReason, (err) => { if (err) throw err; console.log("Blacklist", blacklistReason); }); // Apply to current session - client.blacklist.push(from); + client.blacklist[from] = { + reason: `Your actions triggered auto-banishment`, + when: Date.now() + }; // Apply to future sessions const blacklist = JSON.stringify(client.blacklist); @@ -70,8 +76,26 @@ const addBlacklister = (from, message) => { const castSpell = (client, from, to, incantation) => { let casted = false; client.spells.forEach((_spell) => { - const spell = new _spell(client, from, incantation); - const response = spell.test(); + const spell = new _spell(client, from, to, incantation); + const vars = spell.varsUsed; + let response = null; + + // Only the author can use these spells + if (spell.locked && from !== client.config.author) { + client.say(to, "Blasfemo! That spell is forbidden to you."); + return false; + } + + if (vars > 0) { + // Note: Handle variables in spells + const spellName = spell.spell; + const splitIncantation = incantation.split(spellName); + const variable = splitIncantation[1].trim().split(" "); + spell.incantation = incantation.split(splitIncantation[1])[0]; + response = spell.test.apply(spell, variable.splice(0, vars)); + } else { + response = spell.test(); + } if (spell.casted) { casted = true; @@ -94,11 +118,19 @@ const castSpell = (client, from, to, incantation) => { // For those who cannot handle Esperanto, plebeyoj! const useCheatCode = (client, from, to, cheatCode) => { let cheatCodes = {}; + let variables = ""; + + // Handle variables in cheat code + if (cheatCode.indexOf(" ") > -1) { + splitCheatCode = cheatCode.split(" "); + variables = splitCheatCode.slice(1).join(" "); + cheatCode = splitCheatCode[0]; + } // Generate cheat code client.spells.forEach((_spell) => { const spell = new _spell(client, from, ""); - cheatCodes[spell.cheatCode] = spell.spell; + cheatCodes[spell.cheatCode] = `${spell.spell} ${variables}`; }); if (contains(Object.keys(cheatCodes), cheatCode)) { @@ -110,12 +142,16 @@ const checkBlacklist = (client, from, to, message) => { const now = Date.now(); const lastUsed = client.memory.slice(-1).pop(); const lastTimestamp = lastUsed ? lastUsed.timestamp : 0; + const blacklisters = Object.keys(client.blacklist); + + if (contains(blacklisters, from)) { + const blacklistInfo = blacklisters[from]; + const reason = blacklistInfo.reason; - if (contains(client.blacklist, from)) { // Log the blacklisters for later scrutiny logBlacklisters(from, message); - client.action(to, `is ignoring ${from}. Malsaĝa!`); + client.action(to, `is ignoring ${from} because: ${reason}. Malsaĝa!`); return false; } @@ -126,8 +162,10 @@ const checkBlacklist = (client, from, to, message) => { if (from !== client.config.author) { addBlacklister(from, message); - const banishment = `Malpermesante ${from}`; - client.say(to, `${banishment}! I no longer head your words.`); + let banished = `Malpermesante ${from}!`; + banished = `${banished} Your actions triggered auto-banishment.`; + banished = `${banished} I will no longer heed your words.` + client.say(to, banished); return false; } @@ -142,8 +180,28 @@ client.addListener("message", (from, to, _message) => { const message = _message.toLowerCase(); const triggerWord = client.config.triggerWord; const address = botName.toLowerCase(); + const triggers = { + "!cast": { + vars: " ", + description: "Used to issue an Esperanto spell." + }, + "!summon": { + vars: " ", + description: "Used to send a summoning email to @tilde.team." + }, + "!help BabiliBot": { + vars: "", + description: "PMs user this message." + }, + // "!spells": { + // vars: "", + // description: "PMs spell list to user" + // } + }; + const triggerNames = Object.keys(triggers); if (message.startsWith(triggerWord)) { + // NOTE: entry for !cast const incantation = message.split(triggerWord)[1].trim(); const check = checkBlacklist(client, from, to, incantation); @@ -151,6 +209,7 @@ client.addListener("message", (from, to, _message) => { castSpell(client, from, to, incantation); } } else if (message.startsWith(address)) { + // NOTE: Use the English spells of !cast let cheatCode = message.split(address)[1]; // Why punish formality? @@ -166,6 +225,30 @@ client.addListener("message", (from, to, _message) => { if (check) { useCheatCode(client, from, to, cheatCode); } + } else if (message.startsWith("!summon ")) { + // NOTE: alias for !cast kunvoki - by popular demand + const cheatCode = message.slice(1).trim(); + const check = checkBlacklist(client, from, to, cheatCode); + if (check) { + useCheatCode(client, from, to, cheatCode); + } + } else if (message.startsWith("!botlist")) { + // NOTE: To adhere to https://tilde.team/wiki/?page=irc-bots + let botlist = `${botName} | <${client.config.author}>`; + botlist = `${botlist} | the Esperanto speaking chat bot`; + botlist = `${botlist} | ${triggerNames.join(", ")}`; + client.say(to, botlist); + } else if (message.startsWith(`!help ${address}`)) { + // NOTE: Help information PM'd to requester + client.say(to, `Komprenita, sending help info to ${from}`); + client.say(from, `I answer have ${triggers.length} commands:`); + triggerNames.forEach((name) => { + const trigger = triggers[name]; + let helpText = `${name} ${trigger.vars}`; + helpText = `${helpText} | ${trigger.description}`; + client.say(from, helpText); + }); + client.say(from, "I also respond to: `BabiliBot, `"); } }); diff --git a/spells/banish.js b/spells/banish.js new file mode 100644 index 0000000..b027031 --- /dev/null +++ b/spells/banish.js @@ -0,0 +1,57 @@ +const fs = require("fs"); + +// Used to allow the author to manually banish someone +module.exports = class Banish { + constructor(client, from, incantation) { + this.name = "Banish"; + this.client = client; + this.summoner = from; + this.incantation = incantation; + this.spell = "malpermesi"; + this.cheatCode = "banish"; + this.casted = false; + this.varsUsed = 2; + this.locked = true; + } + + test() { + if (this.incantation === this.spell) { + this.casted = true; + return this.cast(); + } + + return { say: false }; + } + + cast(user, reason) { + let response = { + say: false, + debug: {}, + content: "" + }; + + const subject = "You have been summoned!"; + const text = `Summoning request performed by ${this.summoner}`; + + this.transporter.sendMail({ + to: this.client.email, + subject: subject, + text: text + }, (err, info) => { + console.log("Summon::email", info); + response.debug.email = info; + }); + + this.pusher.note(this.client.device, subject, text, (err, info) => { + console.log("Summon::pusher", info); + response.debug.pusher = info; + }); + + const success = `Superba ${this.summoner}! You have summoned`; + + response.say = true; + response.content = `${success} ${this.client.config.author}!`; + + return response; + } +} \ No newline at end of file diff --git a/spells/summon.js b/spells/summon.js index be7eae6..1fbc300 100644 --- a/spells/summon.js +++ b/spells/summon.js @@ -1,15 +1,19 @@ let nodemailer = require("nodemailer"); const PushBullet = require("pushbullet"); +// Used to summon users into the IRC via email to @tilde.team module.exports = class Summon { - constructor(client, from, incantation) { + constructor(client, from, to, incantation) { this.name = "Summon"; this.client = client; this.summoner = from; + this.channel = to; this.incantation = incantation; - this.spell = "alvoku la kreinton"; + this.spell = "kunvoki"; this.cheatCode = "summon"; this.casted = false; + this.varsUsed = 2; + this.locked = true; this.transporter = nodemailer.createTransport({ sendmail: true, newline: "unix", @@ -21,35 +25,65 @@ module.exports = class Summon { test() { if (this.incantation === this.spell) { this.casted = true; - return this.cast(); + return this.cast.apply(this, arguments); } return { say: false }; } - cast() { + cast(user, reason) { let response = { say: false, debug: {}, content: "" }; + if (!user) { + response.say = true; + response.content = `${from}, malsagxulo!. You must tell me`; + response.content = `${response.content} who to summon.`; + return response; + } + + if (!reason) { + reason = ""; + } + const subject = "You have been summoned!"; - const text = `Summoning request performed by ${this.summoner}`; + let text = `My bot, ${this.client.config.botName}, received`; + text = `${text} a summoning request for you`; + text = `${text} from ${this.summoner}`; + text = `${text} in channel ${this.channel}`; + text = `${text} for reason: ${reason}`; this.transporter.sendMail({ - to: this.client.email, + from: this.client.config.email, + to: `${user}@tilde.team`, subject: subject, text: text }, (err, info) => { - console.log("Summon::email", info); + // DEBUG + // console.log("Summon::email", info); response.debug.email = info; }); - this.pusher.note(this.client.device, subject, text, (err, info) => { - console.log("Summon::pusher", info); - response.debug.pusher = info; - }); + // I obvious don't know everyone's Pushbullet API + if (user === this.client.config.author) { + const self = this; + self.pusher.devices().then(function (res) { + const devices = res.devices; + devices.forEach((device) => { + if (device.nickname === self.client.config.device) { + const iden = device.iden; + self.pusher.note(iden, subject, text, (err, info) => { + // DEBUG + // console.log("Summon::pusher", info); + response.debug.pusher = info; + }); + } + }); + }); + } const success = `Superba ${this.summoner}! You have summoned`;